设计模式之【工厂模式】

设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。

以心法为基础,以武器运用招式应对复杂的编程问题。

表妹:哥啊,我今天看新闻说,欧盟扩大对俄罗斯军工企业及部门的制裁。

我:是啊,俄乌局势这么紧张,欧美国家通过这种手段,试图削弱俄罗斯的战斗力。我们知道,军工厂是输出武器装备的…

现实生活中,有很多工厂,有生产武器装备的,叫做“军工厂”;有生产化学药品的,叫做“化工厂”;还有“芯片工厂”等等。

在我们软件开发中,也有“工厂”这么一说。

什么叫“工厂”?

工厂顾名思义,就是创建产品。该模式封装和管理对象的创建,通俗地讲就是,你new一个对象的时候,直接调用工厂方法就行了。

简单工厂模式

简单工厂模式就是把对类的创建初始化全都交给一个工厂来执行,而用户不需要关心创建的过程是什么样的,只需要告诉工厂,我想要什么就行了。

 

我们以“俄乌战争”为背景,以“军工厂”为例子。我们定义一个武器IArms接口,也就是产品的标准规范。因为战场上需要用到很多类型的武器,这里我们就举两种例子,枪和坦克,也就是两种不同的产品。

 1 public interface IArms {
 2     public void attack() 
 3 }
 4  5 public class Gun implements IArms {
 6     @Override
 7     public void attack() {
 8         System.out.println("我是一支枪");
 9     }
10 }
11 12 public class Tank implements IArms {
13     @Override
14     public void attack() {
15         System.out.println("我是一辆坦克");
16     }
17 }

现在,我们想要生产产品,就需要有工厂。下面就是“军工厂”的代码实现:

 1 public class MilitaryFactory {
 2     public IArms make(String type) {
 3         if (type.equalsIgnoreCase("Gun")) {
 4             return new Gun();
 5         } else if (type.equalsIgnoreCase("Tank")) {
 6             return new Tank();
 7         }
 8         return null;
 9     }
10 }

这个“军工厂”根据前方的订单,创建不同的武器。

接下来,我们看一下客户端如何使用:

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 联系到军工厂的厂长
 4         MilitaryFactory mf = new MilitaryFactory();
 5         // 现在前线需要枪支,下Gun订单
 6         IArms gun = mf.make("Gun");
 7         // 战争激烈,需要坦克支援,下Tank订单
 8         IArms tank = mf.make("Tank");
 9         
10         ... // 省去武器运往前线的过程 
11         
12         // 前线拿到武器之后,加入战斗
13         gun.attack();
14         tank.attack();
15     }
16 }

可以通过下图,更直观的认识简单工厂模式。

 

简单工厂模式的特点

  1. 它是一个具体的类,非接口,非抽象类。有一个重要的make()方法,利用if或者switch分支创建不同的武器并返回。

  2. make()方法通常是静态的,所以也称为静态工厂

简单工厂模式的缺点

  1. 违背了设计原则之【开放封闭原则】。假如现在前线需要飞机支援,除了新增一个飞机武器类(扩展),还需要修改工厂类方法(修改)。

  2. 不同的武器需要不同的额外参数的时候,是不支持的,导致不够灵活。

  3. 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。

为什么使用工厂模式?

在OO设计中,有一个重要的设计原则,就是针对接口而非实现编程。每当我们使用new去实例化一个对象时,用到的就是实现编程,而不是接口。这样一来,代码绑定着具体类,会导致代码更脆弱,缺乏弹性。

工厂方法模式,定义了一个创建对象的接口,但由子类(具体工厂)决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

下图可以很明显的表达这个意思。

 

那么,武器类的结构体系保持不变,主要是将“军工厂”抽象化,其定义了武器的生产接口,但不负责具体的武器生产,将生产任务交给不同的派生类工厂,与其说派生类工厂,不如说是这个军工厂里面的车间,每个车间负责专门生产一种武器。

 1 public interface IArms {
 2     public void attack()
 3 }
 4  5 public class Gun implements IArms {
 6     @Override
 7     void attack() {
 8         System.out.println("我是一支枪");
 9     }
10 }
11 12 public class Tank implements IArms {
13     @Override 
14     void attack() {
15         System.out.println("我是一辆坦克");
16     }
17 }
18 // 以上代码保持不变
19 20 // 生产不同武器的军工厂接口
21 public interface MilitaryFactory {
22     public IArms make()
23 }
24 25 // 生产枪的车间
26 public class GunFactory implements MilitaryFactory {
27     @Override 
28     public IArms make() {
29         return new Gun();
30     }
31 }
32 33 // 生产坦克的车间
34 public class TankFactory implements MilitaryFactory {
35     @Override 
36     public IArms make() {
37         return new Tank();
38     }
39 }
40 41 // 未来,还可以新建一个生产飞机的车间
42 ....

你看,最开始的简单工厂模式就是将所有的生产任务都放在一个车间里完成的。这种模式适用于小型的军工厂,只生产几种武器,而且未来不会频繁地新增其他武器,所以,使用一个车间就够了。

现在的工厂模式,就适用于大型的军工厂,这个工厂生产很多种武器,而且未来还要研发出新的武器。那么,使用一个车间就远远不够了,所以,需要很多个车间,且每个车间负责一种武器的生产任务,未来研发出来的新武器,也另外新建一个车间来负责生产,不影响之前的生产车间。

你看,这样就遵守了设计原则之【开放封闭原则】,使代码具有弹性。

那么,此时客户端又如何调用呢?

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 前线联系到军工厂的枪生产车间的主管
 4         MilitaryFactory gunfactory = new GunFactory();
 5         // 前线联系到军工厂的坦克生产车间的主管
 6         MilitaryFactory tankfactory = new TankFactory();
 7         // 生产车间主管下令具体的生产任务
 8         IArms gun = gunfactory.make();
 9         IArms tank = tankfactory.make();
10         
11         ... // 省略武器运往前线的过程
12            
13         // 前线拿到武器,加入火力进攻
14         gun.attack();
15         tank.attack();
16     }
17 }

你看,这样就不用通过指定类型来创建对象了。

工厂模式的优点

  1. 更符合开-闭原则。新增一种武器时,只需要增加相应的具体武器类和相应的军工厂子类(生产车间)即可。而简单共产模式需要修改工厂类的判断逻辑。

  2. 符合单一职责原则。每个具体工厂类只负责创建对应的武器。而简单工厂中的军工厂类存在复杂的if或者switch逻辑判断。

  3. 不使用静态工厂方法,可以形成基于继承的等级结构。而简单工厂模式的军工厂类使用静态工厂方法。

工厂模式的缺点

  1. 添加新的武器时,除了新增新武器类外,还需要提供与之对应的具体工厂类(生产车间),系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销。

  2. 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能存在用到DOM、反射等技术,增加了系统的实现难度。

简单工厂模式和工厂模式的应用场景

以上两种模式,都各有优缺点,那么,我们来看看它们各自的应用场景。

简单工厂模式适用于业务简单的情况下,或者具体产品很少增加的情况,相比于工厂模式,使用简单工厂模式更具有可读性。

当需要做到灵活、可扩展的时候,就考虑使用工厂模式。比如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择:POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)。后面如果支持新的网络协议,那么就能够在不违背开闭原则的前提下,做到完美的扩展。

抽象工厂模式

 

使用上面两种模式,前方拿到武器后发现,没有弹药,那这仗没法打啊。难道是因为当时我下单的时候,没有说清楚要枪支和子弹,坦克和炮弹吗?

这样就太麻烦了,打仗打的,饭都没时间吃了。

这个时候,抽象工厂模式就派上用场啦。

为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

那么,抽象工厂模式和工厂模式有什么区别呢?

抽象工厂模式是工厂模式的升级版本,他用来创建一组相关或者相互依赖的对象。它与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对多个产品等级结构。

在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。

在抽象工厂模式中,有一个产品族的概念:所谓的产品族,是指位于不同产品等级结构功能相关联的产品组成的家族。抽象工厂模式所提供的一系列产品就组成一个产品族;而工厂方法提供的一系列产品成为一个等级结构。

枪和坦克属于同一个产品等级结构,而子弹和炮弹属于另外一个产品等级结构。在这两个产品等级结构中,枪和子弹是功能相关联的,坦克和炮弹也是功能相关联的。所以,抽象工厂应该将这两个不同的产品等级结构组合在一起,当前线要枪支的时候,才能同时拿到子弹。

如下图所示:

 

 1 // 武器接口
 2 public interface IArms {
 3     public void attack()   // 武器攻击
 4 }
 5  6 public class Gun implements IArms {
 7     @Override
 8     void attack() {
 9         System.out.println("我是一支枪");
10     }
11 }
12 13 public class Tank implements IArms {
14     @Override 
15     void attack() {
16         System.out.println("我是一辆坦克");
17     }
18 }
19 20 // 弹药接口
21 public interface IAmmunition {
22     public void load()  // 弹药上膛
23 }
24 25 public class Bullet implements IAmmunition {
26     @Override
27     void load() {
28         System.out.println("子弹已上膛,等待开枪");
29     }
30 }
31 32 public class Cannonball implements IAmmunition {
33     @Override 
34     void load() {
35         System.out.println("炮弹已上膛,等待发射");
36     }
37 }
38 39 40 // 生产不同武器的军工厂接口
41 public interface MilitaryFactory {
42     public IArms makeArm()   // 生产武器
43     public IAmmunition makeAmmunition()  // 生产弹药
44 }
45 46 // 生产枪和子弹的车间
47 public class GunFactory implements MilitaryFactory {
48     @Override 
49     public IArms makeArm() {
50         return new Gun();
51     }
52     @Override 
53     public IAmmunition makeAmmunition() {
54         return new Bullet();
55     }
56 }
57 58 // 生产坦克和炮弹的车间
59 public class TankFactory implements MilitaryFactory {
60     @Override 
61     public IArms makeArm() {
62         return new Tank();
63     }
64     @Override 
65     public IAmmunition makeAmmunition() {
66         return new Cannonball();
67     }
68 }
69 70 // 未来,还可以新建一个生产飞机和航空弹药的车间
71 ....

接下来,我们看一下,客户端如何调用:

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 前方联系到军工厂生产枪支dan药车间的主管
 4         MilitaryFactory gunFactory = new GunFactory();
 5         // 前方联系到军工厂生产坦克和炮弹车间的主管
 6         MilitaryFactory tankFactory = new TankFactory();
 7         
 8         //
枪支dan药
车间主管下令,开足马力生产枪和子弹  9 IArm gun = gunFactory.makeArm(); 10 IAmmunition bullet = gunFactory.makeAmmunition(); 11 // 坦克和炮弹生产车间主管下令,开足马力生产坦克和炮弹 12 IArm tank = tankFactory.makeArm(); 13 IAmmunition cannonball = tankFactory.makeAmmunition(); 14 15 ... // 省略武器弹药运往前线的过程 16 17 // 武器弹药到达战场 18 bullet.load(); // 子弹上膛 19 gun.attack(); // 开枪射击 20 21 cannonball.load(); // 炮弹上膛 22 tank.attack(); // 坦克攻击 23  } 24 }

现在,你还敢相信我枪里没有子弹了嘛?

抽象工厂模式的优点

  1. 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。

  2. 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

  3. 增加新的具体工厂和产品族很方便,无需修改已有代码,符合开闭原则。

抽象工厂模式的缺点

增加新的产品等级结构会很复杂,需要修改抽象工厂和所有的具体工厂类。

抽象工厂模式的应用场景

当需要创建的对象是一系列相互关联或相互依赖的产品族时,便可以使用抽象工厂模式。

意思就是,一个继承体系中,如果存在着多个等级结构(即存在着多个抽象类),并且分属各个等级结构中的实现类之间存在着一定的关联或约束,就可以使用抽象工厂模式。假如各个等级结构中的实现类之间不存在关联或约束,则使用多个独立的工厂来对产品进行创建,则更适合一点。

总结

设计模式没有对错,关键看你怎么用。

参考

//www.cnblogs.com/yssjun/p/11102162.html

//www.cnblogs.com/toutou/p/4899388.html#_label3

//blog.csdn.net/qq564425/article/details/81082242

《设计模式之禅》