3

工厂模式-将对象的创建封装起来

 2 years ago
source link: https://codeshellme.github.io/2020/12/dp-factory/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

公号:码农充电站pro

主页:https://codeshellme.github.io

工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂工厂方法抽象工厂,它们都是为了更好的创建对象。

所谓的“工厂”,就是用来将创建对象的代码封装起来,因为这部分代码将来变动的几率很大,所以这里的“工厂”的实质作用就是“封装变化”,以便于维护。

其中用到了“针对接口编程,而非针对实现编程”的设计原则。

下面通过一个销售饮料的例子,来对这三种工厂模式进行介绍。

1,简单工厂模式

假如现在有一位老板,想开一家饮料店,该店销售各种口味的饮料,比如苹果味,香蕉味,橙子味等。

将这些饮料用代码来表示,如下:

class Drink {
public void packing() {
class DrinkApple extends Drink {
class DrinkBanana extends Drink {
class DrinkOrange extends Drink {

Drink 类为所有其它味道的饮料的父类。

当有顾客来购买饮料的时候,顾客需要说明想买哪种口味的饮料,然后服务员就去将该口味的饮料取过来,包装好,然后交给顾客。

下面我们用最简单直接的代码,来模拟饮料店和卖饮料的过程,如下:

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 即可。

我将完整的简单工厂代码放在了这里,供大家参考,类图如下:

2,工厂方法模式

简单工厂模式从严格意义来说并不是一个设计模式,而更像一种编程习惯。

工厂方法模式定义了一个创建对象的接口(该接口是一个抽象方法,也叫做“工厂方法”),但由子类(实现抽象方法)决定要实例化的类是哪个。

在工厂方法模式中,父类并不关心子类的具体实现,但是父类给了子类一个“规范”,让子类必须“生成”父类想要的东西。

工厂方法将类的实例化推迟到了子类中,让子类来控制实例化的细节,也就是将创建对象的过程封装了起来。而真正使用实例的是父类,这样就将实例的“实现”从“使用”中解耦出来。因此,工厂方法模式比简单工厂模式更加有“弹性”。

下面我们来看下如何用工厂方法模式来改进上面的代码。

首先,定义 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 {
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 作为一个“框架”,无须改动。

完整的工厂方法代码放在了这里,供大家参考,类图如下:

图中的粉色区域是工厂方法模式的重点关注区。

3,依赖倒置原则

在介绍抽象工厂模式之前,我们先来看看什么是依赖倒置原则

依赖倒置包含了依赖倒置两个词。

我们先来看看“依赖”,“依赖”是指类与类之间的依赖关系。

在本文刚开始时的 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,然后,各个具体类也依赖于 DrinkDrink 是一个广义上的“抽象接口”。

这样,高层组件和低层组件都依赖于 Drink 抽象,关系图变成了下面这样:

之前各个具体类的箭头是向下指的,而现在各个具体类的箭头是向上指的,箭头的方向倒了过来,这就是所谓的依赖倒置

依赖倒置原则使得高层组件和低层组件都依赖于同一个抽象。

那怎样才能避免违反依赖倒置原则呢?有下面三个指导方针:

  • 变量不要持有具体类的引用。
    • 需要 new 对象的地方,要改为工厂方法。
  • 类不要派生自具体类,而要从抽象类或接口派生。
    • 派生自具体类,就会依赖具体类。
  • 子类不要覆盖父类中已实现的方法。
    • 父类中已实现的方法应该被所有子类共享,而不是覆盖。

当然事情没有绝对的,上面三个指导方针,是应该尽量做到,而不是必须做到

4,抽象工厂模式

下面来介绍抽象工厂模式

假如临近春节,饮料店老板为了更好的销售饮料,准备购买一批礼盒,来包装饮料。为了保证礼盒的质量,规定礼盒只能从特定的地方批发,比如北京,上海等。

那我们就定义一个接口,让所有的礼盒都派生自这个接口,如下:

interface DrinkBoxFactory {
String createBox();
class BeiJingBoxFactory implements DrinkBoxFactory {
public String createBox() {
return "BeijingBox";
class ShangHaiBoxFactory implements DrinkBoxFactory {
public String createBox() {
return "ShangHaiBox";

下面需要编写 Drink 类,我们让 Drink 类是一个抽象类,如下:

abstract class Drink {
String flavor;
protected abstract void packing();

需要注意的是,在抽象类 Drink 中有一个抽象方法 packingDrinkpacking 的具体实现交给每个派生类。Drink 类只规定派生类中需要实现一个 packing,但并不关心它的具体实现。

下面编写每种口味的饮料,如下:

class DrinkApple extends Drink {
DrinkBoxFactory boxFactory;
public DrinkApple(DrinkBoxFactory boxFactory) {
this.boxFactory = boxFactory;
this.flavor = "DrinkApple";
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";
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";
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 {
public 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 {
public 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 工具类中的 CalendarDateFormat 等。

(本节完。)


推荐阅读:

单例模式-让一个类只有一个实例

设计模式之高质量代码


欢迎关注作者公众号,获取更多技术干货。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK