浅谈设计模式 - 工厂模式(六)
前言:
在第一篇外面曾经介绍过简略工厂了,然而工厂模式外面不仅仅是简略工厂,还存在工厂办法和形象工厂,并且从严格意义来讲简略工厂不能算是一种设计模式,本次的文章针对工厂的进化来开展讲一讲工厂模式的三种常见模式:简略工厂、工厂办法、形象工厂。
文章目标
- 理解简略工厂这种代码编写模式的长处,回顾工厂模式
- 理解如何从简略工厂扩大到工厂办法以及形象工厂
- 比照工厂办法和形象工厂的异同。
- 总结简略工厂,工厂办法和形象工厂,比照优缺点和特点
如何分别工厂模式
工厂模式个别从类的命名就能够间接看到含意,所以个别状况下很容易看出工厂模式的利用。
- 工厂模式次要是负责对象的创立
- 无论是创建者还是使用者,都是针对一个形象对象的实现。
- 工厂模式最关注的是对象是如何创立的而不是对象的应用。它针对的是创立这一个过程。
工厂模式的具体介绍
简略工厂模式
简略工厂模式的介绍:https://juejin.cn/post/692206...
之前文章曾经介绍过简略工厂模式,咱们间接看一下简略工厂是如何设计的,从严格的意义上来说,简略工厂是一种良好的“编程习惯”,他很好的解耦了创建对象和应用对象这两个不同的过程。做到“繁多职责”的准则
从下面的图当中咱们构建根本的工厂类和对应的实现子类以及对应的产品抽象类。
上面回顾一下简略工厂的优缺点
长处:
- 应用创立工厂的办法,咱们实现了获取具体对象和生产对象的解耦,由生产对象的工厂通过咱们传入的参数生产对应的对象,调用方只须要传递须要生产的对象来实现具体的成果。
- 解耦了创立和被创立的过程。
- 依据不同的逻辑判断生成不同的具体对象。
毛病:
- 每减少一个工厂对象具体的实现类,就须要减少
if/else
不利于保护 - 大量的子类会造成工厂类的代码迅速收缩和臃肿
- 简略工厂的办法个别解决简略的业务逻辑,如果创立逻辑简单不倡议应用。
从下面的优缺点剖析能够晓得,简略工厂并不能齐全解决对象的创立解耦,对于对象的创立细节容易造成耦合,同时如果创立的对象过多容易呈现臃肿的工厂代码。
工厂办法模式
工厂办法模式:定义了创建对象的接口办法,然而具体的创立过程由子类来决定。工厂办法将创立的过程提早到子类,工厂办法是对简略工厂的扩大和降级,为了解决简略工厂毁坏了“凋谢-敞开准则”的问题而做的改良。咱们将具体的产品进行了形象的同时,将创建对象的过程提早到子类进行实现。
工厂办法的结构图
上面为工厂办法的结构图,咱们由简略工厂转变为工厂办法之后,工厂类定义减少了形象的对象创立办法,由子类通过继承的形式实现工厂的形象办法并且实现本人的构建过程。
+ Product 产品类,定义产品的专用办法和抽象类+ ConcreteProduct 产品的具体实现子类,蕴含具体产品的实现+ Factory 工厂类,定义工厂的创立办法以及须要子类继承实现的办法+ ConcreteFactory 工厂的实现类,由子工厂来决定生成的具体产品和定义生产的具体过程。
工厂办法的特点
上面是工厂办法的具体特点
- 创立的过程解耦到子类,由子类决定创立的过程和后果
- 具体的产品和工厂之间存在必要关联,同时能够应用任意子类产品进行替换
- 须要依附继承的模式由子工厂来决定生产的过程,子类决定产品创立的后果
揭示:子类决定创立的后果并不是字面上的创立,而是由调用者决定的。子类决定的是具体针对哪一个实例进行生产,然而生成的具体后果还是管制在创建者的身上
简略工厂和工厂办法有什么区别
- 简略工厂是对产品的创立过程进行“封装”,同时创立新的产品必须改变工厂代码。
- 工厂办法是对简略工厂的降级,工厂办法能够管制具体对象的创立以及由子类来决定具体须要创立哪一个对象。
- 简略工厂只是单纯的解耦创建者和使用者,然而简略工厂无奈扭转创立的后果。
形象工厂模式
形象工厂模式:提供接口,通过定义形象办法的模式,通过实现具体工厂办法实现创立具体对象家族,同时不须要指定非凡的类。
形象工厂的外部往往应用工厂办法进行实现,两者常常被弄混,从构造上来看,他们最大的区别在于工厂办法往往应用继承实现,而形象工厂往往应用外部继承工厂办法的接口实现。辨别工厂办法和形象工厂也是工厂模式的学习要害。
形象工厂的结构图
因为形象工厂更像是对工厂办法的改良,咱们定义形象工厂的结构图,形象工厂的构造相比工厂办法要简单一些:
能够参考形象工厂和工厂办法的结构图,看看两者的异同
+ FactoryInterface 形象工厂接口,定义一批形象对象的生产接口 + ConcreteFactoryA 形象工厂实现类A,实现形象工厂接口。 + ConcreteFactoryB 形象工厂实现类B,实现形象工厂接口。+ ProductA 形象产品A,定义公共的形象办法或者专用属性 + ConcreteProductA 具体实现产品A + ConcreteProductA 具体实现产品A+ ProductB 形象产品B,定义公共的形象办法或者专用属性 + ConcreteProductB 具体实现产品B + ConcreteProductB 具体实现产品B
形象工厂的特点:
- 所有的具体工厂都实现同一个形象工厂接口。
- 生产的后果实现类能够自在实现具体类或者其扩大类的实例。
- 形象工厂的痛点在于扩大一个新的产品生产会造成所有的具体工厂的改变,也蕴含了产品类的变动。
- 形象工厂往往蕴含了一系列的工厂办法
形象工厂和工厂办法的区别
- 形象工厂定义形象接口依附子类实现创立的过程,而工厂办法针对子类实现具体的对象创立细节
- 工厂办法须要应用继承的伎俩实现工厂办法“埋藏”工厂创立具体对象的细节
- 工厂办法对于解决“独立”产品的创立十分无效,而形象工厂往往用于解决生产多个存在关联的产品对象。
理论案例
仍旧参考坦克大战的案例,介绍如何革新坦克大战的具体代码。
模仿场景
仍然以经典的任天堂游戏坦克大战为例,在进入游戏的关卡的时候,会呈现我方的坦克和敌人的坦克,我方坦克和中央坦克不仅形态不同,而且很脆,然而敌人的坦克依据色彩须要打好几枪才会覆灭,那么如果用代码来模仿是什么样的呢?
简略工厂实现:
应用简略工厂实现的代码如下:
应用简略工厂类来治理坦克的创立过程,简略工厂顾名思义,就是简略的将创建对象的过程进行治理。
减少工厂类 TankFactory.java
用工厂来治理具体的坦克创立过程:
/** * 坦克工厂,专门负责生产坦克 * * @author zxd * @version 1.0 * @date 2021/1/25 22:27 */public class TankFactory { /** * 创立坦克 * @return */ public Tank createTank(String check){ Tank tank = null; if(Objects.equals(check, "my")){ tank = new MyTank(); }else if(Objects.equals(check, "mouse")){ tank = new MouseTank(); }else if (Objects.equals(check, "big")){ tank = new BigTank(); }else { throw new UnsupportedOperationException("以后坦克不反对生产"); } return tank; }}
上面是对应的坦克以及坦克的子类实现
/** * 坦克的父类,定义坦克的行为 * * @author zxd * @version 1.0 * @date 2021/1/25 0:14 */public abstract class Tank { /** * 坦克hp */ protected int hp; /** * 坦克子弹 */ protected List<Object> bullet; /** * 挪动的办法s */ abstract void move(); /** * 攻打 */ abstract void attack(); /** * 进行 */ abstract void stop();}
我方的坦克继承坦克的父类:
/** * 我方坦克 * * @author zxd * @version 1.0 * @date 2021/1/25 21:58 */public class MyTank extends Tank { public MyTank() { // 我方坦克假如只有一条命 hp = 1; bullet = new ArrayList<>(); // 初始化增加三发子弹 bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); } @Override void move() { System.err.println("挪动"); } @Override void attack() { System.err.println("攻打中央坦克"); // ..弹出子弹 if(bullet.size() == 0){ System.err.println("没有子弹了"); return; } bullet.remove(bullet.get(bullet.size() -1)); } @Override void stop() { System.err.println("进行"); }}
敌人的坦克如下:
/** * 老鼠坦克 * * @author zxd * @version 1.0 * @date 2021/1/25 22:02 */public class MouseTank extends Tank implements Runnable { public void display() { System.err.println("长得尖尖的,很像老鼠"); } public MouseTank() { // 坦克假如只有一条命 hp = 1; new Thread(this).start(); bullet = new ArrayList<>(); // 初始化增加六发子弹 bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); } @Override void move() { System.err.println("老鼠坦克挪动"); } @Override void attack() { System.err.println("老鼠坦克开枪"); // ..弹出子弹 if (bullet.size() <= 0) { System.err.println("老鼠坦克没有子弹了"); return; } // 老鼠坦克一次性开两枪 bullet.remove(bullet.get(bullet.size() - 1)); } @Override void stop() { System.err.println("进行"); } @Override public void run() { while (true) { // 一旦创立就开始挪动 move(); // 漫无目的开枪 attack(); attack(); // 做完一轮操作歇一秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 随机进行 if (new Random(100).nextInt() % 2 == 0) { stop(); } } }}
最初编写单元测试如下,咱们应用简略工厂生产出不同的坦克,然而客户端不须要纠结生产的细节:
/** * 单元测试 * * @author zxd * @version 1.0 * @date 2021/1/25 22:15 */public class Main { /** * 咱们将生产坦克的过程全副交给了工厂来解决 * 可能还是奇怪,这和方才没有什么区别呀? * 咱们来看下区别: * 1. 创立的过程没有了,尽管是一个简略的new,然而new的过程交给了工厂 * 2. 咱们后续如果要在坦克退出别的货色,只须要去改工厂类和具体的实现类,不须要该此处代码 * 3. 如果不反对的操作,工厂还能够告诉咱们这样做不对 * @param args */ public static void main(String[] args) { TankFactory tankFactory = new TankFactory(); Tank my = tankFactory.createTank("my"); Tank mouse = tankFactory.createTank("mouse"); Tank big = tankFactory.createTank("big"); // 我要一个没有的设计过的坦克 Tank mybig = tankFactory.createTank("mybig"); }/*// 运行后果: Exception in thread "main" 老鼠坦克挪动 巨型坦克挪动 老鼠坦克开枪 巨型坦克开枪 老鼠坦克开枪 java.lang.UnsupportedOperationException: 以后坦克不反对生产 at com.headfirst.factory.use.TankFactory.createTank(TankFactory.java:27) at com.headfirst.factory.use.Main.main(Main.java:33) */}
从下面的代码能够看到,对于坦克的创立和应用过程尽管进行解耦了,然而能够看到创立的过程耦合在了简略工厂的外部,工厂创立的办法耦合了过多的细节,同时如果须要创立新的产品须要改变工厂代码,这违反了凋谢-敞开准则。
针对下面的问题,咱们显然须要应用工厂办法进行改进,咱们让工厂的创立细节提早到子类去实现,子类只须要关注创立的细节,不须要理解客户端的调用,上面咱们针对下面的代码应用工厂办法进行改良。
这种改变也合乎凋谢-敞开准则
工厂办法实现:
从简略工厂能够看出,如果每次批改产品都须要牵动工厂的代码改变,同时针对创立的过程都被“耦合”在独自的工厂创立办法外部,咱们依据工厂办法的结构图看一下如何改良坦克大战的代码:
首先,咱们须要按照工厂办法的定义,将本来的简略工厂类革新为具备工厂办法的工厂,在上面的代码当中,工厂类具备两个办法,一个用于创立具体的对象,由客户端调用,并且提供一个形象的办法,由工厂子类实现并且定义具体的工厂生产过程。
/** * 坦克工厂 * 工厂减少形象办法由子类进行构建 * * @author zxd * @version 1.0 * @date 2021/2/16 17:33 */public abstract class TankFactory { /** * 创立坦克 * @return */ public Tank createTank(String check){ return createConcreteTankMethod(check); } /** * 构建具体产品过程的办法 * @return */ protected abstract Tank createConcreteTankMethod(String check);}
子类不须要关怀createTank()
办法是如何运行的,只须要实现本人的工厂办法同时定义生产的细节提供反对即可。
上面的代码为我方坦克的生产工厂
/** * 我方坦克的创立工厂 * * @author zxd * @version 1.0 * @date 2021/2/16 14:28 */public class OurTankFactory extends TankFactory { @Override public Tank createConcreteTankMethod(String check) { Tank tank = null; if(Objects.equals(check, "my")){ tank = new MyTank(); } return tank; }}
上面的代码为敌人的坦克的生产工厂实现子类。
/** * 敌人坦克的构建工厂 * 老鼠坦克 * @author zxd * @version 1.0 * @date 2021/2/16 14:28 */public class MouseTankFactory extends TankFactory { @Override public Tank createConcreteTankMethod(String check) { Tank tank = null; if(Objects.equals(check, "mouse")){ tank = new MouseTank(); } return tank; }}
通过这样的调整之后,咱们每次减少新的产品,只须要继承具备工厂办法的工厂并且实现对应的办法实现本人的坦克创立细节,就将本来耦合的创立规定从父类从剥离,提早到子类实现,上面来看下单元测试的代码,能够看到工厂的生产具体具体化到子类工厂的外部,而对外仍旧是坦克的生成工厂,这样既合乎依赖倒转
的准则,也不便后续的扩大和更多实现工厂的增加:
/** * 工厂办法的单元测试 * * @author zxd * @version 1.0 * @date 2021/2/16 17:50 */public class Main { public static void main(String[] args) { TankFactory tankFactory = new MouseTankFactory(); TankFactory ourTankFactory = new OurTankFactory(); Tank my = tankFactory.createTank("mouse"); Tank mouse = ourTankFactory.createTank("my"); System.err.println(my); System.err.println(mouse); }/*运行后果: com.headfirst.factory.use.MouseTank@677327b6 老鼠坦克挪动 com.headfirst.factory.use.MyTank@14ae5a5 老鼠坦克开枪 老鼠坦克开枪 */}
工厂办法的问题:尽管工厂办法很好的为咱们解决了创立过程由子类进行构建的问题,然而如果咱们须要往坦克的产品外面提供配对的整机,此时会发现一些问题,咱们的工厂办法只能提供一种产品的生产,如果咱们须要生产很多的产品,工厂办法此时就遇到的瓶颈,因为须要调整继承构造,同时扩大十分不便。
留神点:工厂办法的另一个问题在于他须要依赖继承来实现对象创立过程定义,此时如果改变整个顶层的形象办法会导致依赖磁铁导致所有的子类都须要扭转。如果须要退出多个产品的生产,此时对于所有的子类改变来看都是非常麻烦的事件.
总结:工厂办法在构建一类产品的时候十分无效,然而须要构建很多种产品的时候会产生大量的继承具体化问题
形象工厂的实现:
咱们之前讲过形象工厂实际上是对工厂办法的进一步提取,形象工厂须要的是一系列产品的接口,由子工厂负责一系列产品的接口生产,同时更多的须要依赖组合的模式为具体的产品进行扩大。
在具体的案例代码介绍之前,咱们须要对于案例进行改变,因为之前只存在坦克父类和具体的实现子类,为了具体介绍形象工厂,咱们针对坦克类减少一个大炮类,大炮类提供展现外观的办法,和坦克类的产品齐全不同,咱们须要定义坦克的大炮产品父类和具体的不同实现子类,在形象工厂提供大炮的生产接口形象同时,咱们须要在大炮的类外部组合大炮的对象,为坦克减少不同的大炮外观,上面咱们依据形象工厂的结构图,构建如下的结构图:
咱们参考结构图,定义相似的坦克构造,上面是退出新需要之后的结构图:
依据下面的结构图,咱们先将工厂有具体类革新为工厂接口,不再持有具体的创立过程,将一系列创立的细节散布到子类进行,同时定义接口的形式能够创立多个产品。(这里简化为2个不同的产品)
/** * 坦克工厂,专门负责生产坦克 * * @author zxd * @version 1.0 * @date 2021/1/25 22:27 */public interface TankFactory { /** * 坦克创立办法形象 * @return */ Tank createTank(); /** * 大炮的创立办法 * @return */ Cannon createCannon();}
接下来咱们依据形象工厂接口创立具体的生产工厂,咱们在子类能够返回具体的产品子类也能够返回形象的父类,上面定义我方坦克的工厂类,同时定义一个特定敌人坦克的工厂类。
/** * 我方坦克的创立工厂 * * @author zxd * @version 1.0 * @date 2021/2/16 14:28 */public class OurTankFactory implements TankFactory { /** * 创立自带大炮的坦克 * @return */ public Tank createTanAndCannon() { Tank myTank = createTank(); myTank.setCannon(createCannon()); return myTank; } @Override public Tank createTank() { return new MyTank(); } @Override public Cannon createCannon() { return new Artillery(); }}
敌人坦克的工厂实现子类:敌人的坦克工厂实现子类,能够生产不同形象产品的不同具体实现子类
/** * 敌人坦克的构建工厂 * 老鼠坦克 * @author zxd * @version 1.0 * @date 2021/2/16 14:28 */public class MouseTankFactory implements TankFactory { @Override public MouseTank createTank() { return new MouseTank(); } @Override public Cannon createCannon() { return new RocketLauncher(); }}
接着咱们定义另一个独立的产品,定义顶层的抽象类
/** * 大炮抽象类 * 子类具备不同的大炮模式 * * @author zxd * @version 1.0 * @date 2021/2/16 18:15 */public abstract class Cannon { /** * 外观 */ public abstract void display();}
依据对应下面的形象父类,定义对应点具体实现子类,这里为了简略将两个具体实现子类放到一块:
/** * 火炮 * * @author zxd * @version 1.0 * @date 2021/2/16 19:06 */public class Artillery extends Cannon{ @Override public void display() { System.out.println("火箭炮"); }}/** * 火箭炮 * * @author zxd * @version 1.0 * @date 2021/2/16 19:06 */public class RocketLauncher extends Cannon{ @Override public void display() { System.out.println("火箭炮"); }}
这里扩大了一下坦克类,为坦克类组合了大炮的对象:
/** * 坦克的父类,定义坦克的行为 * * @author zxd * @version 1.0 * @date 2021/1/25 0:14 */public abstract class Tank { /** * 坦克hp */ protected int hp; /** * 坦克子弹 */ protected List<Object> bullet; private Cannon cannon; /** * 挪动的办法s */ public abstract void move(); /** * 攻打 */ public abstract void attack(); /** * 进行 */ public abstract void stop(); public Cannon getCannon() { return cannon; } public void setCannon(Cannon cannon) { this.cannon = cannon; } @Override public String toString() { return "Tank{" + "hp=" + hp + ", bullet=" + bullet + ", cannon=" + cannon + '}'; }}
上面是单元测试代码,咱们在坦克的对象外面设置或者组合其余的对象,并且由工厂提供生产:
/** * 单元测试 * 形象工厂 * @author zxd * @version 1.0 * @date 2021/2/16 16:32 */public class Main { public static void main(String[] args) { TankFactory ourTankFactory = new OurTankFactory(); TankFactory mouseTankFactory = new MouseTankFactory(); Tank ourTankFactoryTank = ourTankFactory.createTank(); Cannon cannon = ourTankFactory.createCannon(); Tank mouseTankFactoryTank = mouseTankFactory.createTank(); Cannon cannon1 = mouseTankFactory.createCannon(); ourTankFactoryTank.setCannon(cannon); mouseTankFactoryTank.setCannon(cannon1); System.err.println("our = " + ourTankFactoryTank); System.err.println("mouse = " + mouseTankFactoryTank); }/* our = Tank{hp=1, bullet=[java.lang.Object@677327b6, java.lang.Object@14ae5a5, java.lang.Object@7f31245a], cannon=com.headfirst.factory.abstractfac.Artillery@6d6f6e28} 老鼠坦克挪动 mouse = Tank{hp=1, bullet=[java.lang.Object@135fbaa4, java.lang.Object@45ee12a7, java.lang.Object@330bedb4, java.lang.Object@2503dbd3, java.lang.Object@4b67cf4d], cannon=com.headfirst.factory.abstractfac.RocketLauncher@7ea987ac} 老鼠坦克开枪 */}
工厂模式的变动:
从下面的案例和具体实现咱们剖析了工厂模式的三种变动:简略工厂、工厂办法、形象工厂。他们的递进秩序也是简略工厂 -> 工厂办法 -> 形象工厂
这种程序。
咱们能够发现简略工厂是一种非常简单的设计思路,他仅仅定义了的创立和应用过程的接口,同时产品具备最根本的形象和继承设计,这类设计往往用于简略的对象构建。而一旦呈现大量的具体对象,简略工厂的代码将会一直的收缩,同时产生很多的if/else
代码。
此时就须要应用工厂办法对于简略工厂的构造进行降级,工厂办法通过继承的形式(定义形象的办法),推延具体对象的创立到子类,工厂父类既能够管制子类的创立后果,同时又不须要关怀具体对象的创立过程,这种设计十分奇妙,很好的解决了工厂的对象创立办法代码臃肿的问题。
然而咱们也发现了问题,工厂办法扩大会导致所有的子类进行强制实现,不利于前期的保护,同时如果须要一系列相干产品的生成,应用工厂办法进行继承实现会造成高度的继承耦合,不利于工厂的产品生产扩大,此时就能够使用形象工厂进行改良,咱们用形象工厂扩大工厂办法,应用接口的模式定义一批接口,由子类工厂进行实现和后续的所有生产细节,同时还能够自定义生产的具体产品。
下面是依据案例对于本次的设计模式进行一个模式的总结,能够看到工厂模式的利用还是十分多的,在WEB畛域最罕用的Spring
框架就是的Bean工厂就是一个十分良好的工厂模式的实际案例。
工厂模式的总结:
上面用一张表格总结工厂模式的三种状态,优缺点以及相干总结:
模式名称 | 简略工厂 | 工厂办法 | 形象办法 |
---|---|---|---|
特点 | 依据产品依照客户端的需要生产不同的具体对象,将生产和应用的过程进行解耦 | 将工厂的创立细节提早到工厂的子类实现。 | 定义一系列工厂办法,由子工厂负责具体的多类产品的生产 |
派生形式 | 须要批改简略工厂的代码 | 顶层工厂减少办法须要所有的子类强制实现。生产多个产品须要改变继承构造 | 扩大产品和生产具体产品十分不便。然而扩大新对象须要改变形象工厂接口 |
长处 | 1. 简略工厂将创建对象的过程和应用对象的过程进行解耦 2. 工厂能够创立生产对象的不同实现子类,扩大子类实现十分不便 | 1. 工厂办法将工厂生产对象的创立细节提早到子类 2. 克服了简略工厂局部毛病,比方合乎凋谢-敞开准则 3. 同样能够对客户端和创建对象工厂进行解耦 | 1. 有利于多个产品的对象创立扩大 2. 将抽象类转变为接口,能够定义更高级的形象。不便向上扩大 3. 相似制订工厂的生产规定,而具体的细节交由实现接口的子类实现 |
毛病 | 1. 工厂扩大新的对象须要改变代码,不合乎凋谢-敞开准则 2. 简略工厂对应简略的创立过程,所以创立过程简单会造成工厂的臃肿 | 1. 不利于保护,退出工厂办法须要扩大所有的子类都须要实现工厂办法 2. 当须要多个产品类的时候,更改会相当的麻烦 | 1. 面对新的产品,须要所有的工厂实现类进行实现。 2. 最大的毛病是难以扩大新的产品,或者说扩大新产品的代价很大 |
总结 | 是一种良好的编码和思考形式,然而严格意义上不能算是设计模式 | 将具体对象的创立过程提早到子类,合乎凋谢-敞开准则 | 形象工厂是对工厂办法的降级,拆散了多个产品的生产同时,子工厂能够对多个产品的生产细节进行自在管制。 |
总结:
本次设计模式的文章内容比拟长,因为本次设计模式尽管是一个设计模式,然而他存在三种“变体”,所以在什么应用哪一种设计还是须要依附具体的需要环境来决定。能够看到该设计模式最容易混同的是工厂办法和形象工厂。心愿通过本文的总结和案例能够让读者更好的理解工厂模式下这两者的应用场景和区别。