文章目录
前言
工厂二字想必大家都不陌生,工厂就是用来建造东西的,我们市面上买的东西比如水杯、玩具、汽车等等都是从工厂生产的,那我们需不需要知道它们是如何生产出来的呢?当然不需要,商家从工厂中直接提货,我们就可以购买了,完全不知道它是如何生产的,这就是工厂方法模式,工厂模式是平时开发过程中最常见的设计模式。工厂模式解决类的实例化问题,它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。它属于创建型模式,工厂模式也经常会和其他设计模式组合使用。
一、基本介绍
1.1 什么是工厂方法模式
工厂模式(Factory Method Pattern)是 Java 中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,实现了创建者和调用者分离,调用者不再需要知道有哪些子类以及应当实例化哪个子类。
试想一下,但你去麦当劳买一个汉堡,只需要告诉收银员要一个xx汉堡。过一会就会有一个此类型的汉堡被制作出来,而你完全不需要知道这个汉堡是怎么被制作出来的。这个例子中你就是客户端代码,麦当劳就是工厂,负责生产汉堡。汉堡是接口,而具体的某一种汉堡,比如说香辣鸡腿堡,就是实现了汉堡接口的类。
1.2 定义与特点
工厂方法模式定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。个人感觉就像武侠剧中的万剑归宗,一生十、十生百、万剑不离其宗、万法归一。
方向 | 说明 |
---|---|
主要解决 | 主要解决接口选择的问题 |
何时使用 | 我们明确地计划不同条件下创建不同实例时 |
如何解决 | 让其子类实现工厂接口,返回的也是一个抽象的产品 |
关键代码 | 创建过程在其子类执行 |
应用实例 | 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现 |
优点 | 1、一个调用者想创建一个对象,只要知道其名称就可以了 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以 3、屏蔽产品的具体实现,调用者只关心产品的接口 |
缺点 | 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加, 在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。 |
使用场景 | 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时 3、设计一个连接服务器的框架,需要三个协议POP3、IMAP、HTTP,可以把这三个作为产品类,共同实现一个接口 |
二、实现方式
按实际业务场景划分,工厂方法模式有 2 种不同的实现方式,分别是简单工厂模式、工厂方法模式。我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品。但简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
2.1 简单工厂模式
2.1.1 模式定义
简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。
因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。当类型过多时不利于系统的扩展维护,旦异常,整个系统将受影响,且工厂类代码会非常臃肿,违背高聚合原则。
2.1.2 模式实现
在简单工厂模式中,专门定义一个类来负责创建其他类的实例,可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。简单工厂模式包含如下角色:
组成(角色) | 关系 | 作用 |
---|---|---|
抽象产品 | 具体产品的父类 | 负责描述所有实例所共有的公共接口 |
具体产品 | 抽象产品的子类;工厂类创建的目标类 | 创建目标,所有创建的对象都充当这个角色的某个具体类的实例。 |
工厂 | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。代码演示如下:
-
首先创建一个工厂接口创建抽象产品类,定义具体产品的公共接口,代码如下:
public interface Product { void show(); }
-
定义了接口,该来实现它了,创建实现相同接口的具体类(继承抽象产品类),定义生产的具体产品,代码如下:
public class ConcreteProduct1 implements Product { @Override public void show() { System.out.println("具体产品1显示..."); } } public class ConcreteProduct2 implements Product { @Override public void show() { System.out.println("具体产品2显示..."); } }
-
创建工厂类,通过创建静态方法从而根据传入不同参数创建不同具体产品类的实例,被创建的实例具有共同的父类或接口,代码如下:
public class SimpleFactoryPattern { public static Product makeProduct(int kind) { switch (kind) { case 1: return new ConcreteProduct1(); case 2: return new ConcreteProduct2(); default: return null; } } }
-
使用Factory调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例对象,代码如下:
public class FactoryPatternDemo { public static void main(String[] args) { SimpleFactoryPattern.makeProduct(1).show(); SimpleFactoryPattern.makeProduct(2).show(); } }
从上面的示例可以看到,简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象,明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
2.1.3 模式分析
在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数,就可以获取你所需要的对象,而无须知道其创建细节。将对象的创建和对象本身业务处理分离,这样可以降低系统的耦合度,使得两者修改起来都相对容易。如果需要新增新产品,直接修改工厂类,而不需要在原代码中修改,更加容易扩展。
简单工厂模式最大的问题在于工厂类的职责相对过重,很明显工厂类集中了所有实例的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响。而且一旦增加新的产品需要修改工厂类的判断逻辑,这样就会造成工厂逻辑过于复杂,这一点违反高内聚的责任分配原则。所以简单工厂模式适合业务简单,工厂类不会经常变更的情况。
2.2 工厂方法模式
2.2.1 模式定义
工厂方法模式(Factory Method Pattern),又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法模式包含Product(抽象产品)、ConcreteProduct(具体产品)、Factory(抽象工厂)、ConcreteFactory(具体工厂)等角色,如下图所示:
在上面提到了简单工厂模式违背了开闭原则,而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
2.2.2 模式实现
-
创建抽象产品,定义具体产品的公共接口,代码如下:
public interface Car { public void run(); }
-
创建抽象工厂,定义具体工厂的公共接口,代码如下:
public interface CarFactory { Car createCar(); }
-
定义了接口,该来实现它了,创建实现相同接口的具体类(继承抽象产品类),定义生产的具体产品,代码如下:
/** * 3.1、创建工厂的产品(奥迪) */ public class AoDi implements Car { @Override public void run() { System.out.println("我是奥迪汽车.."); } } /** * 3.2、创建工厂另外一种产品(宝马) */ public class Bmw implements Car { @Override public void run() { System.out.println("我是宝马汽车..."); } }
-
具体产品
/** * 4.1、创建工厂方法调用接口的实例(奥迪) */ public class AoDiFactory implements CarFactory { @Override public Car createCar() { return new AoDi(); } } /** * 4.2、创建工厂方法调用接口的实例(宝马) */ public class BmwFactory implements CarFactory { @Override public Car createCar() { return new Bmw(); } }
-
创建工厂的具体实例
public class FactoryMethodPattern { public static void main(String[] args) { Car aoDi = new AoDiFactory().createCar(); Car jiLi = new BmwFactory().createCar(); aoDi.run(); jiLi.run(); } }
厂方法模式是简单工厂模式的进一步抽象和推广,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
2.2.3 模式分析
在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。而且在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
三、小结
工厂模式使用了继承、接口、抽象等机制,可以看出设计模式并不是独立的,而是相互之间有关系和区别的,在学习的时候我们要善于总结设计模式之间的共同之处和不同之处,活学活用,才能在以后的大型项目中选择正确的开发方式,事半功倍。
- https://design-patterns.readthedocs.io/zh-cn/latest/creational_patterns/simple_factory.html
- https://design-patterns.readthedocs.io/zh-cn/latest/creational_patterns/factory_method.html