设计模式 | 简单工厂模式及典型应用

<h3 id="前言">前言

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。

本文主要介绍简单工厂模式及典型应用,内容如下:

  • 简单工厂模式的介绍
  • 简单工厂模式的典型应用及源码分析
    • Calendar 类获取日历类对象
    • JDBC 获取数据库连接
    • LoggerFactory 获取 Logger 对象

工厂模式是最常用的一类创建型设计模式,包括 抽象工厂模式,工厂方法模式和简单工厂模式 这三种,简单工厂模式是其中最简单的一种

简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。

因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式,但不属于GOF23种设计模式

Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product

Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。

ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法

在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须直接使用new关键字来创建对象,它是工厂模式家族中最简单的一员

抽象产品类 Video,定义了抽象方法 produce()

public abstract class Video {
    public abstract void produce();
}

具体产品类 JavaVideo 和 PythonVideo,都继承了抽象产品类 Video

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("录制Java课程视频");
    }
}

public class PythonVideo extends Video {
@Override
public void produce() {
System.out.println("录制Python课程视频");
}
}

工厂类实现的两种方法:使用if-else判断和使用反射来创建对象

public class VideoFactory {
    /**
     * 使用if else 判断类型,type 为 Java 则返回 JavaVideo, type为Python则返回 PythonVideo
     */
    public Video getVideo(String type) {
        if ("java".equalsIgnoreCase(type)) {
            return new JavaVideo();
        } else if ("python".equalsIgnoreCase(type)) {
            return new PythonVideo();
        }
        return null;
    }
/**
 * 使用反射来创建对象
 */
public Video getVideo(Class c) {
    Video video = null;
    try {
        video = (Video) Class.forName(c.getName()).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return video;
}

}

使用一个客户端来调用工厂类

public class Test {
    public static void main(String[] args) {
        VideoFactory videoFactory = new VideoFactory();
        Video video1 = videoFactory.getVideo("python");
        if (video1 == null) {
            return;
        }
        video1.produce();
    Video video2 = videoFactory.getVideo(JavaVideo.class);
    if (video2 == null) {
        return;
    }
    video2.produce();
}

}

输出

录制Python课程视频
录制Java课程视频

示例.简单工厂模式类图

Test 类通过传递参数给 VideoFactory.getVideo() 来获取对象,创建对象的逻辑交给了工厂类 VideoFactory 来完成

简单工厂模式的主要优点如下:

  • 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

简单工厂模式的主要缺点如下:

  • 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
  • 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护,且违背开闭原则。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

适用场景

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。

Calendar 抽象类,该类的子类有 BuddhistCalendarJapaneseImperialCalendarGregorianCalendarRollingCalendar

getInstance方法,根据参数获取一个Calendar子类对象,该方法实际将参数传给 createCalendar 方法,createCalendar 在根据参数通过 providerswitch 或者 if-else 创建相应的子类对象

以下为 Java8 中的 Calendar 类代码,Java7 中的实现为 if-else 方式

public static Calendar getInstance(TimeZone zone,Locale aLocale) {
    return createCalendar(zone,aLocale);
}

private static Calendar createCalendar(TimeZone zone,Locale aLocale) {
CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class,aLocale).getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone,aLocale);
} catch (IllegalArgumentException iae) {
}
}

Calendar cal = null;

if (aLocale.hasExtensions()) {
    String caltype = aLocale.getUnicodeLocaleType("ca");
    if (caltype != null) {
        switch (caltype) {
            case "buddhist":
                cal = new BuddhistCalendar(zone,aLocale); break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone,aLocale); break;
            case "gregory":
                cal = new GregorianCalendar(zone,aLocale); break;
        }
    }
}
if (cal == null) {
    if (aLocale.getLanguage() == "th" &amp;&amp; aLocale.getCountry() == "TH") {
        cal = new BuddhistCalendar(zone,aLocale);
    } else if (aLocale.getVariant() == "JP" &amp;&amp; aLocale.getLanguage() == "ja" &amp;&amp; aLocale.getCountry() == "JP") {
        cal = new JapaneseImperialCalendar(zone,aLocale);
    } else {
        cal = new GregorianCalendar(zone,aLocale);
    }
}
return cal;

}

Calendar的继承关系

可以看到抽象产品角色工厂角色都由 Calendar 担任,具体产品角色Calendar 的子类担任

一般JDBC获取MySQL连接的写法如下:

//加载MySql驱动
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","123456");

首先通过反射加载驱动类 com.mysql.jdbc.Driver 类,然后再通过 DriverManager 获取连接

看看 com.mysql.jdbc.Driver 的代码,该类主要的内容是静态代码块,其会随着类的加载一块执行

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

静态代码块:new 一个 Driver 类并注册到 DriverManager 驱动管理类中

public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da) throws SQLException {
    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver,da));
    } else {
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);
}

其中的 registeredDrivers 是一个 CopyOnWriteArrayList 对象

private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet
一篇CopyOnWriteArrayList的文章:https://www.cnblogs.com/chengxiao/p/6881974.html

再通过 DriverManager.getConnection 获取连接对象的主要代码如下:通过for循环从已注册的驱动中(registeredDrivers)获取驱动,尝试连接,成功则返回连接

private static Connection getConnection(String url,java.util.Properties info,Class caller) throws SQLException {
    // ...省略...
    println("DriverManager.getConnection(\"" + url + "\")");
    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then skip it.
        if(isDriverAllowed(aDriver.driver,callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url,info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    // ...省略...
}

Connection 接口及子类实现关系

工厂角色为 DriverManager 类,抽象产品角色为 Connection,具体产品角色则很多

查看 LoggerFactory 类的 getLogger 方法,可看到调用了 iLoggerFactory.getLogger(),其中 iLoggerFactory 是一个接口

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}

iLoggerFactory 接口只有一个 getLogger 方法

public interface ILoggerFactory {
    Logger getLogger(String var1);
}

查看其子类依赖关系

iLoggerFactory接口子类的依赖关系

再看一个子类 LoggerContext 对 ILoggerFactory 的实现

image

可看到这是通过 if-else 方式的简单工厂模式

Logger 接口及子类实现关系

工厂角色为 iLoggerFactory 接口的子类如 LoggerContext,抽象产品角色为 Logger,具体产品角色为 Logger 的子类,主要是 NOPLoggerLogger

参考:
刘伟:设计模式Java版
网java设计模式精讲 Debug 方式+内存分析

欢迎评论、转发、分享,您的支持是我最大的动力

更多内容可访问我的个人博客:http://laijianfeng.org

关注【小旋锋】微信公众号,及时接收博文推送

推荐阅读:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结