首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >设计模式 | 创建型 | 工厂模式

设计模式 | 创建型 | 工厂模式

作者头像
被水淹没
发布2023-02-25 19:05:39
发布2023-02-25 19:05:39
5960
举报
文章被收录于专栏:迈向架构师迈向架构师

今天学习分享的是工厂模式:

工厂模式 Factory

将创建对象移交给工厂来处理。

大部分工厂类都是以 “Factory” 这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat、Calender。 除此之外,工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(), 但有的也命名为 getInstance()、createInstance()、newInstance(), 有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等。

GoF 把简单工厂并入工厂方法,这里单独摘出来成为一部分:

工厂模式


简单工厂 Simple Factory

介绍

简单工厂模式描述了一个类,它拥有一个包含大量条件语句的构建方法,可根据方法的参数来选择对何种产品进行初始化并将其返回。

适用场景

当每个对象的创建逻辑都比较简单的时候,将多个对象的创建逻辑放到一个工厂类中。

实现方式

  1. 新建一个工厂类。
  2. 新建方法,通过入参判断返回生成的对象。

优缺点

优点:

  • 代码简单
  • 避免耦合

与其他模式的关系

  • 大多数情况下,简单工厂是引入工厂方法或抽象工厂模式时的一个中间步骤。

示例

简单用 if 判断参数,并生成对象给调用者。

代码语言:javascript
复制
public class CollectionFactory {
    public static Collection create(String type) throws Exception {
        switch (type) {
            case "list":
                return new ArrayList();
            case "linked":
                return new LinkedList();
            case "deque":
                return new ArrayDeque();
            default:
                throw new Exception("传递的用户类型错误。");
        }
    }
}

如果对象是可复用的,也可以把对象事先创建好并缓存起来:

代码语言:javascript
复制
public class RuleConfigParserFactory {
    private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
    
    static {
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        cachedParsers.put("yaml", new YamlRuleConfigParser());
        cachedParsers.put("properties", new PropertiesRuleConfigParser());
    }   
    
    public static IRuleConfigParser createParser(String configFormat) {
        if (configFormat == null || configFormat.isEmpty()) {
            return null;//返回null还是IllegalArgumentException全凭你自己说了算
        }
        IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
        return parser;
    }
}

实例

JDK类库中广泛使用了简单工厂模式,如工具类 java.text.DateFormat,它用于格式化一个本地日期或者时间。

代码语言:javascript
复制
java.text.DateFormat#getDateInstance()
java.text.DateFormat#getDateInstance(int style)
java.text.DateFormat#getDateInstance(int style,Locale locale)

密钥生成器:

代码语言:javascript
复制
javax.crypto.KeyGenerator#getInstance(String algorithm)

创建密码器:

代码语言:javascript
复制
javax.crypto.Cipher#getInstance(String transformation);

工厂方法 Factory Method

介绍

工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

Head First 定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

适用场景

当每个对象的 创建逻辑 都比较 复杂 的时候,

为了避免设计一个过于庞大的简单工厂类时,将创建逻辑拆分得更细,

让每个对象的创建逻辑独立到各自的工厂类中。

  • 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
  • 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。
  • 如果你希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。

实现方式

工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用。

对象仍将通过 new 运算符创建,只是该运算符改在工厂的方法中调用罢了。

  1. 让所有产品都遵循同一接口。该接口必须声明对所有产品都有意义的方法。
  2. 在创建类中添加一个空的工厂方法。该方法的返回类型必须遵循通用的产品接口。
  3. 在创建者代码中找到对于产品构造函数的所有引用。将它们依次替换为对于工厂方法的调用,同时将创建产品的代码移入工厂方法。你可能需要在工厂方法中添加临时参数来控制返回的产品类型。
  4. 现在,为工厂方法中的每种产品编写一个创建者子类,然后在子类中重写工厂方法,并将基本方法中的相关创建代码移动到工厂方法中。
  5. 如果应用中的产品类型太多,那么为每个产品创建子类并无太大必要,这时你也可以在子类中复用基类中的控制参数。
  6. 如果代码经过上述移动后,基础工厂方法中已经没有任何代码,你可以将其转变为抽象类。如果基础工厂方法中还有其他语句,你可以将其设置为该方法的默认行为。

优缺点

优点:

  • 避免耦合
  • 单一职责
  • 开闭原则

缺点:

  • 代码变得复杂

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
  • 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 工厂方法是模板方法模式的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。

示例

工厂接口与工厂

代码语言:javascript
复制
public interface ICollectionFactory {
    Collection createCollection();
}
public class ListFactory implements ICollectionFactory {
    @Override
    public Collection createCollection() {
        // 可以把复杂的初始化过程放在这里
        return new ArrayList();
    }
}
public class LinkedFactory implements ICollectionFactory {
    @Override
    public Collection createCollection() {
        // 可以把复杂的初始化过程放在这里
        return new LinkedList();
    }
}
public class DequeFactory implements ICollectionFactory {
    @Override
    public Collection createCollection() {
        // 可以把复杂的初始化过程放在这里
        return new ArrayDeque();
    }
}

工厂的工厂:

代码语言:javascript
复制
public class CollectionFactoryMap {
    private static final Map<String, ICollectionFactory> cachedFactories = new HashMap<>();

    static {
        cachedFactories.put("list", new ListFactory());
        cachedFactories.put("linked", new LinkedFactory());
        cachedFactories.put("deque", new DequeFactory());
    }

    public static ICollectionFactory getCollectionFactory(String type) {
        if (type == null || type.isEmpty()) {
            return null;
        }
        ICollectionFactory factory = cachedFactories.get(type.toLowerCase());
        return factory;
    }
}

实例

JDK (这里有些是简单工厂,具体判断是看把有没有实例化推迟到子类):

代码语言:javascript
复制
java.util.ResourceBundle#getBundle()
java.text.NumberFormat#getInstance()
java.nio.charset.Charset#forName()
java.net.URLStreamHandlerFactory#createURLStreamHandler(String) (根据协议返回不同的单例对象)
java.util.EnumSet#of()
javax.xml.bind.JAXBContext#createMarshaller() 及其他类似的方法。

JDK 日历类 Calendar:

这个是简单工厂:

java.util.Calendar#getInstance()

代码语言:javascript
复制

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
  //...
  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) {
        // fall back to the default instantiation
      }
    }

    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" && aLocale.getCountry() == "TH") {
        cal = new BuddhistCalendar(zone, aLocale);
      } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") {
        cal = new JapaneseImperialCalendar(zone, aLocale);
      } else {
        cal = new GregorianCalendar(zone, aLocale);
      }
    }
    return cal;
  }
  //...
}

slf4j

这个是工厂方法:

org.slf4j.LoggerFactory#getILoggerFactory()

代码语言:javascript
复制
public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        // support re-entrant behavior.
        // See also http://jira.qos.ch/browse/SLF4J-97
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

抽象工厂 Abstract Factory

介绍

抽象工厂是一种创建型设计模式,它能创建一系列相关或相互依赖的对象,而无需指定其具体类。

Head First 定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类

适用场景

  • 如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望代码基于产品的具体类进行构建,在这种情况下,你可以使用抽象工厂。
  • 如果你有一个基于一组抽象方法的类,且其主要功能因此变得不明确,那么在这种情况下可以考虑使用抽象工厂模式。
  • 如果你的程序中并不涉及产品系列的话,那就不需要抽象工厂。

实现方式

  1. 以不同的产品类型与产品变体为维度绘制矩阵。
  2. 为所有产品声明抽象产品接口。然后让所有具体产品类实现这些接口。
  3. 声明抽象工厂接口,并且在接口中为所有抽象产品提供一组构建方法。
  4. 为每种产品变体实现一个具体工厂类。
  5. 在应用程序中开发初始化代码。该代码根据应用程序配置或当前环境,对特定具体工厂类进行初始化。然后将该工厂对象传递给所有需要创建产品的类。
  6. 找出代码中所有对产品构造函数的直接调用,将其替换为对工厂对象中相应构建方法的调用。

优缺点

优点:

  • 可以确保同一工厂生成的产品相互匹配。
  • 可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。你可以将产品生成代码抽取到同一位置,使得代码易于维护。
  • 开闭原则。向应用程序中引入新产品变体时,你无需修改客户端代码。

缺点:

  • 引入众多的接口和类,代码可能会比之前更加复杂。

与其他模式的关系

  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
  • 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。

示例

王争老师举的例子:

前面简单工厂提到的可复用写法 RuleConfigParserFactory,通过不同文件类型加载解析器。

如果多出了分类,如系统解析类、规则解析类等,增加的工厂数量将倍增:

代码语言:javascript
复制
针对规则配置的解析器:基于接口IRuleConfigParser
JsonRuleConfigParser
XmlRuleConfigParser
YamlRuleConfigParser
PropertiesRuleConfigParser

针对系统配置的解析器:基于接口ISystemConfigParser
JsonSystemConfigParser
XmlSystemConfigParser
YamlSystemConfigParser
PropertiesSystemConfigParser

让一个工厂负责创建多个不同类型的对象(IRuleConfigParserISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。具体的代码实现如下所示:

代码语言:javascript
复制
public interface IConfigParserFactory {
    IRuleConfigParser createRuleParser();
    ISystemConfigParser createSystemParser();
    //此处可以扩展新的parser类型,比如IBizConfigParser
}

public class JsonConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new JsonRuleConfigParser();
    }

    @Override
    public ISystemConfigParser createSystemParser() {
        return new JsonSystemConfigParser();
    }
}

public class XmlConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new XmlRuleConfigParser();
    }

    @Override
    public ISystemConfigParser createSystemParser() {
        return new XmlSystemConfigParser();
    }
}

// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码

这里是我参考 refactoringguru 实现的例子:

refactoringguru 文中示例的是 “跨平台 UI 组件”,目的为适配多平台下 UI 组件的渲染,结构为:

  • GUIFactory
    • WinFactory
    • MacFactory
  • Button
    • WinButton
    • MacButton
  • Checkbox
    • WinCheckbox
    • MacCheckbox
  • Application
  • ApplicationConfigurator

而我的文件结构为:

  • PrintFactory
    • PrinterHTMLFactory
    • PrinterPDFFactory
  • MyArrayList
    • MyArrayListHTML
    • MyArrayListPDF
  • MyLinkedList
    • MyLinkedListHTML
    • MyLinkedListPDF
  • Application
  • ApplicationConfigurator

大体思路是:

算法题中有很多数据结构类型,比如数组,链表,树等结构。这边假设要实现一个可视化的数据结构。

上面简单工厂实现了一个简单容器工厂 CollectionFactory,用来创建不同的数据类型。

到了这里,结构有了,想要输出成 html 动态显示,或者输出成 pdf 打印版,于是利用抽象工厂来实现:

代码语言:javascript
复制
/**
 * 【抽象工厂接口】声明了一组能返回不同抽象产品的方法。
 * 这些产品属于同一个系列且在高层主题或概念上具有相关性。
 * 同系列的产品通常能相互搭配使用。
 * 系列产品可有多个变体,但不同变体的产品不能搭配使用。
 */
public interface PrinterFactory {
    MyArrayList createArrayList();
    MyLinkedList createLinkedList();
    //  这里可以扩展类型
}

/**
 * 【具体工厂】可生成属于同一变体的系列产品。
 * 工厂会确保其创建的产品能相互搭配使用。
 * 具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进行实例化。
 */
public class PrinterHTMLFactory implements PrinterFactory {
    @Override
    public MyArrayList createArrayList() {
        return new MyArrayListHTML();
    }
    @Override
    public MyLinkedList createLinkedList() {
        return new  MyLinkedListHTML();
    }
    //  这里可以扩展类型
}

/**
 * 每个【具体工厂】中都会包含一个相应的产品变体。
 */
public class PrinterPDFFactory implements PrinterFactory {
    @Override
    public MyArrayList createArrayList() {
        return new MyArrayListPDF();
    }
    @Override
    public MyLinkedList createLinkedList() {
        return new MyLinkedListPDF();
    }
    //  这里可以扩展类型
}

/**
 * 系列产品中的特定产品必须有一个基础【产品接口】。所有产品变体都必须实现这个接口。
 */
public interface MyArrayList {
    void print();
}

/**
 * 【具体产品】由相应的具体工厂创建。
 */
public class MyArrayListHTML implements MyArrayList{
    @Override
    public void print() {
        System.out.println("打印ArrayList样式的HTML");
    }
}

/**
 * 这是另一个产品。
 * 所有产品都可以互动,但是只有相同具体变体的产品之间才能够正确地进行交互。
 */
public class MyArrayListPDF implements MyArrayList{
    @Override
    public void print() {
        System.out.println("打印ArrayList样式的PDF");
    }
}

// MyLinkedList 同上,略
// MyLinkedListHTML 同上,略
// MyLinkedListPDF 同上,略

/**
 * 【客户端】代码仅通过抽象类型(PrinterFactory、arrayList 和 LinkedLlist)使用工厂和产品。
 * 这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
 */
public class Application {

    private PrinterFactory factory;
    private MyArrayList arrayList;
    private MyLinkedList linkedList;

    public Application(PrinterFactory factory) {
        this.factory = factory;
    }

    public void create(){
        arrayList = factory.createArrayList();
        linkedList = factory.createLinkedList();
    }

    public void print() {
        arrayList.print();
        linkedList.print();
    }
}

/**
 * 【程序】会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初始化阶段)。
 */
public class ApplicationConfigurator {

    public static void main(String[] args) throws Exception {

        PrinterFactory factory;

        if ("pdf".equals(args[0])) {
            factory = new PrinterPDFFactory();
        } else if ("html".equals(args[0])) {
            factory = new PrinterHTMLFactory();
        } else {
            throw new Exception("未知的输出方式");
        }
        Application application = new Application(factory);
        application.create();
        application.print();
    }

}

这样的话,如后续果我想扩展类型,或者开放给别人扩展,别人只需要扩展一个新的类型即可,不需要改动工厂。

实例

以下是来自核心 Java 程序库的一些示例:

代码语言:javascript
复制
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()

JDBC

代码语言:javascript
复制
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//连接相应的数据库
Connection connection = DriverManager.getConnection("url", "username", "password");

这个私有 getConnection 方法中会遍历已经注册数据库驱动,

也就是我们加载的 MySQL 数据库驱动 Class.forName("com.mysql.jdbc.Driver")

DriverManager#getConnection():

代码语言:javascript
复制
// private static Connection getConnection(String url, java.util.Properties info, Class<?> caller)
···
for(DriverInfo aDriver : registeredDrivers){
···
    Connection con = aDriver.driver.connect(url, info);
···
}

代码中 aDriver.driver 返回的正是 MySQL 实现的具体驱动,

也就是一个具体的子工厂 com.mysql.jdbc.Driver

它的抽象工厂接口则是 Java 提供的 com.sql.Driver

总结:

Java 定义了一种 JDBC 连接数据库的方式和相应的接口,各个厂商只需要实现它即可。

客户端在选择使用哪种数据库的时候不用关心具体方法的调用或者返回哪一个类,只需要使用工厂中提供的工厂方法即可。

工厂模式与 DI 容器

DI 容器:依赖注入容器(Dependency Injection Container)。

一个工厂类只负责某个类对象或者某一组相关类对象的创建,而 DI 容器负责的是整个应用中所有类对象的创建。

DI 容器底层最基本的设计思路就是基于工厂模式的。 DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。 当应用程序需要使用某个类对象的时候,直接从容器中获取即可。 正是因为它持有一堆对象,所以这个框架才被称为“容器”。

DI 容器的核心功能
  • 配置解析
  • 对象创建
  • 对象生命周期管理
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 迈向架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工厂模式 Factory
  • 简单工厂 Simple Factory
    • 介绍
    • 适用场景
    • 实现方式
    • 优缺点
    • 与其他模式的关系
    • 示例
    • 实例
  • 工厂方法 Factory Method
    • 介绍
    • 适用场景
    • 实现方式
    • 优缺点
    • 与其他模式的关系
    • 示例
    • 实例
      • JDK 日历类 Calendar:
      • slf4j
  • 抽象工厂 Abstract Factory
    • 介绍
    • 适用场景
    • 实现方式
    • 优缺点
    • 与其他模式的关系
    • 示例
    • 实例
      • JDBC
      • 工厂模式与 DI 容器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档