工厂模式是用于实例化对象的模式。在一般情况下,如果我们想使用一个对象,我们必须在使用它之前实例化它。在工厂模式下,我们不需要调用构造函数来获取对象实例,而是通过工厂方法获取对象实例。可以说,调用构造函数的过程封装在工厂模式中。
1、工厂模式的优势
表面上看,工厂模式似乎是多余的,因为工厂方法还是通过构造方法来创建对象实例,没有直接创建实例方便。其实从设计的角度来说,直接创建对象是一种高度耦合的方式,属于完全耦合。比如B类的对象是在A类的方法中创建和使用的,B类设计的变化对A类的影响很大,根据开闭原理(OCP),B类的扩展在修改时是开闭的。B类增加一种施工方法,符合OCP原理。如果为A类增加了B类的新构造方法,A类必须修改B类的创建方法(如图1所示)。
图1 B类增加施工方法对a类的影响
如果B类依赖于A类以上,修改范围会扩大。这时,工厂模式就可以展现它的优势了(如图2所示)。
图2工厂模式
从工厂模式我们可以看到,如果B被扩展,不会影响使用的目标类(A类和C类),扩展问题只能通过修改工厂方法createB来解决。因此,目标类和B类之间的耦合度降低。
其次,有些类的构造方法参数太多,参数对实例的影响很大,会增加开发人员的失败成本。使用工厂模式可以降低开发人员记忆参数的成本。在原生Java中,线程池更具代表性。ThreadPoolExecutor有四种构造方法。根据不同的构造方法参数,可以导出三种类型的线程池:可缓存线程池、定数线程池和单点线程池。在Executors类中,提供了这些线程池的工厂方法。当不熟悉线程池的参数时,使用工厂方法可以降低团队的失败概率。
第三,减少依赖。减少依赖是减少耦合的一种方式。从面向对象的设计原则来看,最直接的设计原则是LSP原则(替换原则)。LSP原理在各种设计模式中被频繁使用。当LSP与工厂模式结合时,也能表现出很强的解耦效果。例如,当一个功能可能被高度扩展或实现时,我们不应该直接创建特定的实现子类,比如下图中的武器系统。
具体的战斗单位可以从武器界面推导出来,但是不确定使用哪个武器单位。使用时,需要根据环境条件创建特定的单位。这个时候,最好的办法就是不要直接制造特定的武器单位(坦克、导弹等)。),而是由工厂类给我们提供具体的武器例子。武器单位一旦扩展,我们只需要在工厂类的基础上进行扩展,不需要对目标类进行大量修改。这是LSP和工厂模式带来的进步。
2、几种常见的工厂模式
,实现工厂类主要有三种模式:1。静态工厂模式。2.工厂方法模式。3.抽象工厂。创建对象和创建对象实例时,几种工厂模式之间存在一些差异。但本质上是一样的。
2.1 静态工厂模式
静态工厂模式也称为工厂方法模式,可以基于 具体条件来创建对象并返回。这种形式的工厂模式适合构造方法简单的对象(或简单参数)。当目标对象的派生结构简单的时候,我们可以使用if/else的方式进行创建。如果派生结构略多,可以可以使用switch/case的方式进行创建(如图4所示)。
图4 简单工厂
在一些企业软件中,很多业务被服务化(一个业务流程设计成一个服务对象,通过固定方法启用或停止,通常要设计成含有固定功能的服务接口),服务接口的实现可能存在上百个或者更多。这个时候采用if/else或者switch/case的方式肯定是不合理的,那么我们可以用代理模式结合工厂模式进行创建。也可以将具体的服务类写成key-value形式的配置文件,通过反射进行创建。当增加服务的时候,也无需修改工厂类,只需要修改配置文件即可(如图5所示)。

图5 配置文件与工厂模式结合
从图5来看,当服务接口再次被扩展的时候,我们也无需修改工厂类,只要在配置文件config.properties中增加配置项即可。这样的方式除了可以替换、还可以增加系统可靠度(功能冗余、在一些特殊环境下可以实现表决器的效果)。如果熟悉IOC,也是可以与工厂模式结合。无论使用哪一种,我们的目的都是为了让整个系统在扩展、维护、测试等角度上更加容易和便捷。
2.2 工厂方法模式
工厂方法模式与静态工厂模式存在的区别还是很大的。因为静态工厂模式创建的方式简单,根据简单的需求类型返回实例。因此工厂方法不支持太复杂的构造方法。如果构造方法的参数特别复杂,静态工厂模式就无法适应了。尤其是线程池类,参数的不同直接影响了线程池类型是不同的。这个时候就可以使用工厂方法模式,它可以根据各种参数的不同,来选择创建不同的对象或者调用不同的构造方法。
打个比喻来说,在工厂方法模式下,消费者(使用者)不需要关心产品是怎么样产生的(对象是调用哪些构造方法进行创建的),只向厂家(工厂类)提出各种要求(提供有些必要的规则参数),厂家根据要求生产产品并返回给消费者。生产产品的过程完全由厂家来控制,至于如何改变产品工艺(如创建具体的对象)、流程(如参数计算)消费者都不用去考虑。
从创建实例的过程上来看工厂方法模式肯定比静态工厂模式更丰富一些。静态工厂模式相当于消费者和商家在对话,是简单的产品需求和供应关系。在工厂方法模式下,相当于消费者和厂家对话,消费者可以提出更丰富细致的要求,而厂家也会根据需求生产更精准的产品。在工厂方法模式下,工厂类在创建对象时,并不是简单的创建对象并返回,而是有可能加入一些计算逻辑。
在Java原生API中,有很多工具类都采用了这样的工厂模式,例如Executors类、还有Swing的BorderFactory等,我们就不进行画图说明和举例说明了。
2.3 抽象工厂模式
抽象工厂可以解决更为抽象的产品问题,如果说静态工厂和工厂方法是解决单一产品问题的话,抽象工厂模式可以解决产品族的问题。产品族不再是单一的产品(同一类型的对象),而是多个聚合产品(多个类型的对象)。例如当你出席酒会的时候,除了衣服外,你还要搭配鞋子、领带等装饰,这个时候就构成了产品族。
使用抽象工厂模式进行设计时,就需要将产品族和各类产品进行抽象设计,顶层的抽象工厂最为产品族的生产管控者,它管控着各分类产品的制造工厂(如图6所示)。

图6 抽象工厂模式
在图6中,AbstractFactory(抽象类)相当于维护了整个产品族。ShoesFactory和CoatFactory是AbstractFactory的子类(具体的生产工厂),它们二者完成具体对象的创建工作。当客户(消费者)在使用产品族的时候,不能直接通过AbstractFactory进行创建,必须通过工厂生产者FactoryProucer来完成(创建抽象工厂对象)。工厂生产者是抽象工厂模式中,新增的一个角色,它的目的是创建具体的抽象工厂,当然我们也可以将抽象工厂和FactoryProucer进行合并(提供一个或多个静态方法,创建AbstractFactory实例)。
从抽象工厂模式下,我们可以看到它有一个巨大的缺点,如果产品族的产品发生了扩充,我们就要修改抽象工厂类,并且增加具体的实现工厂。这个时候我们可以进行一些调整,例如创建一个产品族的聚合类——礼服类,礼服类可以包含衣服、鞋子、领带等,如果再进行产品族的扩充,我们可以在礼服类上增加更多的聚合变量,或者通过继承关系来扩充产品。具体采用哪种方案可以参考OCP原则和CRP原则(合成聚合复用原则),采用一种最合适的方式。
虽然有人说抽象工厂模式才是真正的工厂模式,其实这是一种误解,只有合适的模式才是最好的模式。从软件工程的角度来说,无意义的增加设计难度,会造成软件可靠度下降、增加项目成本,这是一种自大的技术病,是工程领域禁止的行为。新的框架和设计模式是为了应对新的业务、新的问题而产生的技术迭代,目的是为了降成本、提高系统可靠度、扩展性。所以无论采用哪种工厂模式,一定要采用最合适的模式。