公号:码农充电站pro
主页:https://codeshellme.github.iohtml
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的建立对象。java
所谓的“工厂”,就是用来将建立对象的代码封装起来,由于这部分代码未来变更的概率很大,因此这里的“工厂”的实质做用就是“封装变化”,以便于维护。git
其中用到了“针对接口编程,而非针对实现编程”的设计原则。github
下面经过一个销售饮料的例子,来对这三种工厂模式进行介绍。shell
假如如今有一位老板,想开一家饮料店,该店销售各类口味的饮料,好比苹果味,香蕉味,橙子味等。编程
将这些饮料用代码来表示,以下:设计模式
class Drink { public void packing() { // } } class DrinkApple extends Drink { } class DrinkBanana extends Drink { } class DrinkOrange extends Drink { }
Drink
类为全部其它味道的饮料的父类。app
当有顾客来购买饮料的时候,顾客须要说明想买哪一种口味的饮料,而后服务员就去将该口味的饮料取过来,包装好,而后交给顾客。框架
下面咱们用最简单直接的代码,来模拟饮料店和卖饮料的过程,以下:ide
class DrinkStore { public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; } }
可是这种实现方式有个问题,就是当须要下架旧饮料或上架新饮料的时候,会致使下面这部分代码被频繁的修改:
if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); }
那这就违背了设计原则中的开闭原则:代码应该对扩展开发,对修改关闭。因此咱们须要对该代码进行改进,那如何修改呢?
简单工厂模式告诉咱们要将相似这样的代码封装到一个类里边,这个类就叫作简单工厂类,该类中提供一个方法,它能够生产咱们所须要的各类对象。
下面用代码来模拟这个简单工厂类,以下:
class SimpleDrinkFactory { public Drink createDrink(String flavor) { Drink drink; // 这段容易被频繁修改的代码,被封装到了工厂类中 if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } }
能够看到,createDrink
方法完成了建立 Drink
的任务。
createDrink
方法也能够定义成静态方法,优势是在使用 createDrink
方法时不须要再建立对象,缺点是不能再经过继承的方式来改变 createDrink
方法的行为。
下面来看如何使用 SimpleDrinkFactory
类:
class DrinkStore { private SimpleDrinkFactory factory; public DrinkStore(SimpleDrinkFactory factory) { this.factory = factory; } public Drink sellDrink(String flavor) { Drink drink = factory.createDrink(flavor); drink.packing(); return drink; } }
能够看到,咱们将 SimpleDrinkFactory
类的对象做为 DrinkStore
类的一个属性,通过改进后的 sellDrink
方法就不须要再被频繁修改了。若是再须要上架下架饮料,则去修改简单工厂类 SimpleDrinkFactory
便可。
我将完整的简单工厂代码放在了这里,供你们参考,类图以下:
简单工厂模式从严格意义来讲并非一个设计模式,而更像一种编程习惯。
工厂方法模式定义了一个建立对象的接口(该接口是一个抽象方法,也叫作“工厂方法”),但由子类(实现抽象方法)决定要实例化的类是哪一个。
在工厂方法模式中,父类并不关心子类的具体实现,可是父类给了子类一个“规范”,让子类必须“生成”父类想要的东西。
工厂方法将类的实例化推迟到了子类中,让子类来控制实例化的细节,也就是将建立对象的过程封装了起来。而真正使用实例的是父类,这样就将实例的“实现”从“使用”中解耦出来。所以,工厂方法模式比简单工厂模式更加有“弹性”。
下面咱们来看下如何用工厂方法模式来改进上面的代码。
首先,定义 DrinkStoreAbstract
,它是一个抽象父类:
abstract class DrinkStoreAbstract { // final 防止子类覆盖 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用实例 drink.packing(); return drink; } // 子类必须实现 protected abstract Drink factoryMethod(String flavor); }
上面代码中 factoryMethod
方法就是所谓的工厂方法,它是一个抽象方法,子类必须实现该方法。
factoryMethod
方法负责生成对象,使用对象的是父类,而实际生成对象的则是子类。
接下来定义一个具体的 DrinkStore
类,它是 DrinkStoreAbstract
的子类:
class DrinkStore extends DrinkStoreAbstract { @Override public Drink factoryMethod(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } }
能够看到,子类中的 factoryMethod
方法有了具体的实现。
若是须要上架下架饮料,则去修改子类中的工厂方法 factoryMethod
便可。而 DrinkStoreAbstract
做为一个“框架”,无须改动。
完整的工厂方法代码放在了这里,供你们参考,类图以下:
图中的粉色区域是工厂方法模式的重点关注区。
在介绍抽象工厂模式以前,咱们先来看看什么是依赖倒置原则。
依赖倒置包含了依赖和倒置两个词。
咱们先来看看“依赖”,“依赖”是指类与类之间的依赖关系。
在本文刚开始时的 sellDrink
方法是这么写的:
public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; }
sellDrink
方法依赖了三个具体类,若是饮料的味道继续增长的话,那么 sellDrink
方法将依赖更多的具体类。
这会致使只要任意一个具体类发生改变,sellDrink
方法就不得不去改变,也就是类 A 须要改变(A 也是一个具体类)。
在上面这个关系图中,A
称为高层组件,各个具体类称为低层组件,因此在这幅图中,高层组件依赖了低层组件。
依赖倒置原则是指“要依赖抽象,而不依赖具体类”。更具体来讲就是,高层组件不该该依赖低层组件,高层组件和低层组件都应该依赖抽象类。
那么怎样才能达到依赖倒置原则呢?工厂方法就能够!
在通过工厂方法模式的改造以后,最终的 DrinkStoreAbstract
类中的 sellDrink
方法变成了下面这样:
public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用实例 drink.packing(); return drink; }
这使得 sellDrink
的所在类再也不依赖于具体类,而依赖于一个抽象方法 factoryMethod
,而 factoryMethod
方法依赖于 Drink
,而后,各个具体类也依赖于 Drink
,Drink
是一个广义上的“抽象接口”。
这样,高层组件和低层组件都依赖于 Drink
抽象,关系图变成了下面这样:
以前各个具体类的箭头是向下指的,而如今各个具体类的箭头是向上指的,箭头的方向倒了过来,这就是所谓的依赖倒置。
依赖倒置原则使得高层组件和低层组件都依赖于同一个抽象。
那怎样才能避免违反依赖倒置原则呢?有下面三个指导方针:
固然事情没有绝对的,上面三个指导方针,是应该尽可能作到,而不是必须作到。
下面来介绍抽象工厂模式。
假如临近春节,饮料店老板为了更好的销售饮料,准备购买一批礼盒,来包装饮料。为了保证礼盒的质量,规定礼盒只能从特定的地方批发,好比北京,上海等。
那咱们就定义一个接口,让全部的礼盒都派生自这个接口,以下:
interface DrinkBoxFactory { String createBox(); } class BeiJingBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "BeijingBox"; } } class ShangHaiBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "ShangHaiBox"; } }
下面须要编写 Drink
类,咱们让 Drink
类是一个抽象类,以下:
abstract class Drink { String flavor; protected abstract void packing(); }
须要注意的是,在抽象类 Drink
中有一个抽象方法 packing
,Drink
将 packing
的具体实现交给每一个派生类。Drink
类只规定派生类中须要实现一个 packing
,但并不关心它的具体实现。
下面编写每种口味的饮料,以下:
class DrinkApple extends Drink { DrinkBoxFactory boxFactory; public DrinkApple(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkApple"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkBanana extends Drink { DrinkBoxFactory boxFactory; public DrinkBanana(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkBanana"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkOrange extends Drink { DrinkBoxFactory boxFactory; public DrinkOrange(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkOrange"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } }
能够看到每种口味的饮料中都有一个 boxFactory
对象,在 packing
时,从 boxFactory
中获取礼盒。
注意 boxFactory
是一个接口类对象,而不是一个具体类对象,所以,boxFactory
的具体对象是由客户决定的。
而后,DrinkStoreAbstract
仍是沿用工厂方法模式中的定义,以下:
abstract class DrinkStoreAbstract { // final 防止子类覆盖 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用实例 drink.packing(); return drink; } // 子类必须实现 protected abstract Drink factoryMethod(String flavor); }
而后咱们实现使用北京礼盒包装的Store 和使用上海礼盒包装的Store,以下:
class BeijingDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new BeiJingBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } } class ShangHaiDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new ShangHaiBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } }
通过这么一些列的改动,咱们到底作了些什么呢?事情的原由是老板须要一些礼盒来包装饮料,这就是需求增长了。
所以咱们引入了一个抽象工厂,即 DrinkBoxFactory
,这个接口是全部礼盒工厂的父类,它给了全部子类一个“规范”。
抽象工厂模式提供了一个接口,用于建立相关对象的家族。该模式旨在为客户提供一个抽象接口(本例中就是 DrinkBoxFactory
接口),从而去建立一些列相关的对象,而不需关心实际生产的具体产品是什么,这样作的好处是让客户从具体的产品中解耦。
最终,客户是这样使用 DrinkStoreAbstract
的,以下:
public class AbstractMethod { public static void main(String[] args) { DrinkStoreAbstract beiStore = new BeijingDrinkStore(); beiStore.sellDrink("apple"); beiStore.sellDrink("banana"); beiStore.sellDrink("orange"); DrinkStoreAbstract shangStore = new ShangHaiDrinkStore(); shangStore.sellDrink("apple"); shangStore.sellDrink("banana"); shangStore.sellDrink("orange"); } }
完整的抽象工厂模式代码放在了这里,供你们参考,类图以下:
从该图能够看出,抽象工厂模式比工厂方法模式更复杂了一些,另外仔细观察它们两个的类图,各自所关注的地方(粉红色区域)也是不同的。
工厂方法与抽象工厂的相同点都是将对象的建立封装起来。不一样点是:
工厂模式使用到的设计原则有:
本篇文章介绍了三种工厂模式,工厂模式在实际应用中很是普遍,好比 Java 工具类中的 Calendar 和 DateFormat 等。
(本节完。)
推荐阅读:
欢迎关注做者公众号,获取更多技术干货。