简单工厂(Simple Factory)

Intent

在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

Class Diagram

简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。

这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。

设计模式 - 简单工厂 - 图1

Implementation

  1. public interface Product {
  2. }
  1. public class ConcreteProduct implements Product {
  2. }
  1. public class ConcreteProduct1 implements Product {
  2. }
  1. public class ConcreteProduct2 implements Product {
  2. }

以下的 Client 类包含了实例化的代码,这是一种错误的实现。如果在客户类中存在这种实例化代码,就需要考虑将代码放到简单工厂中。

  1. public class Client {
  2. public static void main(String[] args) {
  3. int type = 1;
  4. Product product;
  5. if (type == 1) {
  6. product = new ConcreteProduct1();
  7. } else if (type == 2) {
  8. product = new ConcreteProduct2();
  9. } else {
  10. product = new ConcreteProduct();
  11. }
  12. // do something with the product
  13. }
  14. }

以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。

  1. public class SimpleFactory {
  2. public Product createProduct(int type) {
  3. if (type == 1) {
  4. return new ConcreteProduct1();
  5. } else if (type == 2) {
  6. return new ConcreteProduct2();
  7. }
  8. return new ConcreteProduct();
  9. }
  10. }
  1. public class Client {
  2. public static void main(String[] args) {
  3. SimpleFactory simpleFactory = new SimpleFactory();
  4. Product product = simpleFactory.createProduct(1);
  5. // do something with the product
  6. }
  7. }

简单工厂模式总结

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

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

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

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

适用场景

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

    简单工厂模式的典型应用及源码分析

    Calendar 类获取日历类对象

    Calendar 抽象类,该类的子类有 BuddhistCalendar、JapaneseImperialCalendar、GregorianCalendar、RollingCalendar等
    getInstance方法,根据参数获取一个Calendar子类对象,该方法实际将参数传给 createCalendar 方法,createCalendar 在根据参数通过 provider 或 switch 或者 if-else 创建相应的子类对象
    以下为 Java8 中的 Calendar 类代码,Java7 中的实现为 if-else 方式

    1. private static Calendar createCalendar(TimeZone zone,
    2. Locale aLocale)
    3. {
    4. CalendarProvider provider =
    5. LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
    6. .getCalendarProvider();
    7. if (provider != null) {
    8. try {
    9. return provider.getInstance(zone, aLocale);
    10. } catch (IllegalArgumentException iae) {
    11. // fall back to the default instantiation
    12. }
    13. }
    14. Calendar cal = null;
    15. if (aLocale.hasExtensions()) {
    16. String caltype = aLocale.getUnicodeLocaleType("ca");
    17. if (caltype != null) {
    18. switch (caltype) {
    19. case "buddhist":
    20. cal = new BuddhistCalendar(zone, aLocale);
    21. break;
    22. case "japanese":
    23. cal = new JapaneseImperialCalendar(zone, aLocale);
    24. break;
    25. case "gregory":
    26. cal = new GregorianCalendar(zone, aLocale);
    27. break;
    28. }
    29. }
    30. }
    31. if (cal == null) {
    32. // If no known calendar type is explicitly specified,
    33. // perform the traditional way to create a Calendar:
    34. // create a BuddhistCalendar for th_TH locale,
    35. // a JapaneseImperialCalendar for ja_JP_JP locale, or
    36. // a GregorianCalendar for any other locales.
    37. // NOTE: The language, country and variant strings are interned.
    38. if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
    39. cal = new BuddhistCalendar(zone, aLocale);
    40. } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
    41. && aLocale.getCountry() == "JP") {
    42. cal = new JapaneseImperialCalendar(zone, aLocale);
    43. } else {
    44. cal = new GregorianCalendar(zone, aLocale);
    45. }
    46. }
    47. return cal;
    48. }

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

    JDBC 获取数据库连接

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

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

    首先通过反射加载驱动类 com.mysql.jdbc.Driver 类,然后再通过 DriverManager 获取连接
    看看 com.mysql.jdbc.Driver 的代码,该类主要的内容是静态代码块,其会随着类的加载一块执行

    1. public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    2. public Driver() throws SQLException {
    3. }
    4. static {
    5. try {
    6. DriverManager.registerDriver(new Driver());
    7. } catch (SQLException var1) {
    8. throw new RuntimeException("Can't register driver!");
    9. }
    10. }
    11. }

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

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

    其中的 registeredDrivers 是一个 CopyOnWriteArrayList 对象

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

    CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为”写时复制器”,Java并发包中类似的容器还有CopyOnWriteSet 一篇CopyOnWriteArrayList的文章:https://www.cnblogs.com/chengxiao/p/6881974.html
    再通过 DriverManager.getConnection 获取连接对象的主要代码如下:通过for循环从已注册的驱动中(registeredDrivers)获取驱动,尝试连接,成功则返回连接

    1. private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    2. // ...省略...
    3. println("DriverManager.getConnection(\"" + url + "\")");
    4. for(DriverInfo aDriver : registeredDrivers) {
    5. // If the caller does not have permission to load the driver then skip it.
    6. if(isDriverAllowed(aDriver.driver, callerCL)) {
    7. try {
    8. println(" trying " + aDriver.driver.getClass().getName());
    9. Connection con = aDriver.driver.connect(url, info);
    10. if (con != null) {
    11. // Success!
    12. println("getConnection returning " + aDriver.driver.getClass().getName());
    13. return (con);
    14. }
    15. } catch (SQLException ex) {
    16. if (reason == null) {
    17. reason = ex;
    18. }
    19. }
    20. } else {
    21. println(" skipping: " + aDriver.getClass().getName());
    22. }
    23. }
    24. // ...省略...
    25. }

    Connection 接口及子类实现关系
    工厂角色为 DriverManager 类,抽象产品角色为 Connection,具体产品角色则很多

    Logback 中的 LoggerFactory 获取 Logger 对象

    查看 LoggerFactory 类的 getLogger 方法,可看到调用了 iLoggerFactory.getLogger(),其中 iLoggerFactory 是一个接口 ```java public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name); }

public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName()); } ``` Logger 接口及子类实现关系
工厂角色为 iLoggerFactory 接口的子类如 LoggerContext,抽象产品角色为 Logger,具体产品角色为 Logger 的子类,主要是 NOPLogger 和 Logger 类