![506eda37a87c5aaf52ef955d841a3bae.png](https://i-blog.csdnimg.cn/blog_migrate/f58d9e3a7c62b92cc9878be42a611802.jpeg)
工厂模式是实例化对象的一种模式。在一般的场景下,如果我们要使用一个对象,必须先实例化,再使用。在工厂模式下,我们无需通过调用构造方法来得到对象实例,而是通过工厂方法得到对象实例,可以说工厂模式下,调用构造方法的过程被封装了。
1、工厂模式的优势
只从表面来看,工厂模式的做法似乎多此一举,因为工厂方法中依然会通过构造方法等方式创建对象实例,不如直接创建实例来的方便。实际上,从设计角度来说,直接创建对象是一种耦合度较高的方式,属于全耦合。
例如在A类的方法中创建了B类的对象并使用,B类设计的更改对A类的影响是巨大的。从开放封闭原则(OCP)上来说,B类的扩展是开放的,修改时关闭的。给B类增加一个构造方法,是符合OCP原则的。如果B类新增的构造方法是针对A类增加的,那么A类就必须修改B类的创建方式(如图1所示)。
![767e05a9c5a5ddf03f284bc9caf34705.png](https://i-blog.csdnimg.cn/blog_migrate/2d865044dd63acaa3007a00444286414.jpeg)
图1 B类增加构造方法对A类的影响
如果对B类依赖的不只是A,那么修改的范围将会扩大。这个时候工厂模式就能体现出它的优势(如图2所示)。
![dd4f928e0bc25c6b82dd885897af5880.png](https://i-blog.csdnimg.cn/blog_migrate/7bda30b7f64e8b504ded7a057f7af4b1.jpeg)
图2 工厂模式
从工厂模式下,我们可以看出,如果对B进行扩展,不会影响到使用的目标类上(类A和类C),只要修改工厂方法createB就可以解决扩展问题。这样使得目标类和B的耦合度下降了。
其次,一些类的构造方法的参数过多,而且参数对实例的作用影响巨大,这样会增加开发者故障成本。采用工厂模式,可以降低开发者记忆参数的成本。在原生Java中,比较具有代表性的就是线程池。线程池ThreadPoolExecutor拥有四个构造方法,根据构造方法参数设定的不同,可以衍生出三种类型的线程池:可缓存线程池、 固定数量线程池、单点线程池。在Executors类中,就提供这些线程池的工厂方法。当不熟悉线程池的参数时,使用工厂方法可以降低团队的故障概率。
第三、降低依赖性。降低依赖性就是降低耦合的一种方式。从面向对象设计原则角度来说,最直接的设计原则就是LSP原则(替换原则)。LSP原则在各种设计模式中的使用频率非常的高。当LSP和工厂模式相结合也能体现出强大的解耦效果。例如,当一个功能可能会被高度扩展或者实现的时候,在使用该功能的时候,我们不应该直接创建具体的实现子类,比如下图中的武器系统。
![3c6f34452e1da5faa485745c02d89ff0.png](https://i-blog.csdnimg.cn/blog_migrate/2ed4ffbff9db7010378b1dc661a8cddf.jpeg)
武器接口可派生出具体战斗单元,具体使用哪个武器单元并不确定,在使用的时候,需要根据环境条件来创建具体单元。这时最佳的方式不是直接创建具体的武器单元(坦克、导弹等),而是由工厂类提供给我们具体的武器实例。一旦发生武器单元发生了扩展,我们只需要在工厂类的基础上进行扩展即可,不需要大量修改目标类。这就是LSP和工厂模式带来的先进性。
2、几种常见的工厂模式
在具体实现工厂类的方式上,主要有三种实现模式:1、静态工厂模式。2、工厂方法模式。3、抽象工厂模式。几种工厂模式在创建对象的时候在创建对象实例的时候有些区别。但本质上都是相同的。
2.1 静态工厂模式
静态工厂模式也称简单工厂模式,它可以根据具体条件来创建对象并返回。这种形式的工厂模式适合构造方法简单的对象(或简单参数)。当目标对象的派生结构简单的时候,我们可以使用if/else的方式进行创建。如果派生结构略多,可以可以使用switch/case的方式进行创建(如图4所示)。
![ec0e2f920ba67a97a870f3483c60a83c.png](https://i-blog.csdnimg.cn/blog_migrate/c88f829c740892a7996f1e60d6ffab81.jpeg)
图4 简单工厂
在一些企业软件中,很多业务被服务化(一个业务流程设计成一个服务对象,通过固定方法启用或停止,通常要设计成含有固定功能的服务接口),服务接口的实现可能存在上百个或者更多。这个时候采用if/else或者switch/case的方式肯定是不合理的,那么我们可以用代理模式结合工厂模式进行创建。也可以将具体的服务类写成key-value形式的配置文件,通过反射进行创建。当增加服务的时候,也无需修改工厂类,只需要修改配置文件即可(如图5所示)。
![ae246d55b6a43d91fef3489d637f7a09.png](https://i-blog.csdnimg.cn/blog_migrate/f12ec0e18ecde3505259a4552e4d8607.jpeg)
图5 配置文件与工厂模式结合
从图5来看,当服务接口再次被扩展的时候,我们也无需修改工厂类,只要在配置文件config.properties中增加配置项即可。这样的方式除了可以替换、还可以增加系统可靠度(功能冗余、在一些特殊环境下可以实现表决器的效果)。如果熟悉IOC,也是可以与工厂模式结合。无论使用哪一种,我们的目的都是为了让整个系统在扩展、维护、测试等角度上更加容易和便捷。
2.2 工厂方法模式
工厂方法模式与静态工厂模式存在的区别还是很大的。因为静态工厂模式创建的方式简单,根据简单的需求类型返回实例。因此工厂方法不支持太复杂的构造方法。如果构造方法的参数特别复杂,静态工厂模式就无法适应了。尤其是线程池类,参数的不同直接影响了线程池类型是不同的。这个时候就可以使用工厂方法模式,它可以根据各种参数的不同,来选择创建不同的对象或者调用不同的构造方法。
打个比喻来说,在工厂方法模式下,消费者(使用者)不需要关心产品是怎么样产生的(对象是调用哪些构造方法进行创建的),只向厂家(工厂类)提出各种要求(提供有些必要的规则参数),厂家根据要求生产产品并返回给消费者。生产产品的过程完全由厂家来控制,至于如何改变产品工艺(如创建具体的对象)、流程(如参数计算)消费者都不用去考虑。
从创建实例的过程上来看工厂方法模式肯定比静态工厂模式更丰富一些。静态工厂模式相当于消费者和商家在对话,是简单的产品需求和供应关系。在工厂方法模式下,相当于消费者和厂家对话,消费者可以提出更丰富细致的要求,而厂家也会根据需求生产更精准的产品。在工厂方法模式下,工厂类在创建对象时,并不是简单的创建对象并返回,而是有可能加入一些计算逻辑。
在Java原生API中,有很多工具类都采用了这样的工厂模式,例如Executors类、还有Swing的BorderFactory等,我们就不进行画图说明和举例说明了。
2.3 抽象工厂模式
抽象工厂可以解决更为抽象的产品问题,如果说静态工厂和工厂方法是解决单一产品问题的话,抽象工厂模式可以解决产品族的问题。产品族不再是单一的产品(同一类型的对象),而是多个聚合产品(多个类型的对象)。例如当你出席酒会的时候,除了衣服外,你还要搭配鞋子、领带等装饰,这个时候就构成了产品族。
使用抽象工厂模式进行设计时,就需要将产品族和各类产品进行抽象设计,顶层的抽象工厂最为产品族的生产管控者,它管控着各分类产品的制造工厂(如图6所示)。
![608cd8d5ae2ffaf6e0a06fe3bea3359d.png](https://i-blog.csdnimg.cn/blog_migrate/b86079a6c5fb5c4612c3c33a0bc8a98c.jpeg)
图6 抽象工厂模式
在图6中,AbstractFactory(抽象类)相当于维护了整个产品族。ShoesFactory和CoatFactory是AbstractFactory的子类(具体的生产工厂),它们二者完成具体对象的创建工作。当客户(消费者)在使用产品族的时候,不能直接通过AbstractFactory进行创建,必须通过工厂生产者FactoryProucer来完成(创建抽象工厂对象)。工厂生产者是抽象工厂模式中,新增的一个角色,它的目的是创建具体的抽象工厂,当然我们也可以将抽象工厂和FactoryProucer进行合并(提供一个或多个静态方法,创建AbstractFactory实例)。
从抽象工厂模式下,我们可以看到它有一个巨大的缺点,如果产品族的产品发生了扩充,我们就要修改抽象工厂类,并且增加具体的实现工厂。这个时候我们可以进行一些调整,例如创建一个产品族的聚合类——礼服类,礼服类可以包含衣服、鞋子、领带等,如果再进行产品族的扩充,我们可以在礼服类上增加更多的聚合变量,或者通过继承关系来扩充产品。具体采用哪种方案可以参考OCP原则和CRP原则(合成聚合复用原则),采用一种最合适的方式。
虽然有人说抽象工厂模式才是真正的工厂模式,其实这是一种误解,只有合适的模式才是最好的模式。从软件工程的角度来说,无意义的增加设计难度,会造成软件可靠度下降、增加项目成本,这是一种自大的技术病,是工程领域禁止的行为。新的框架和设计模式是为了应对新的业务、新的问题而产生的技术迭代,目的是为了降成本、提高系统可靠度、扩展性。所以无论采用哪种工厂模式,一定要采用最合适的模式。