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

公号:码农充电站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");
    }
}

完整的抽象工厂模式代码放在了这里,供大家参考,类图如下:

在这里插入图片描述

从该图可以看出,抽象工厂模式比工厂方法模式更复杂了一些,另外仔细观察它们两个的类图,各自所关注的地方(粉红色区域)也是不一样的

工厂方法与抽象工厂的相同点都是将对象的创建封装起来。不同点是:

  • 工厂方法主要关注将类的实例化推迟到子类中
  • 抽象工厂主要关注创建一系列相关的产品家族

5,总结

工厂模式使用到的设计原则有:

  • 针对接口编程,而非针对实现编程。
  • 开闭原则。
  • 依赖倒置原则。
  • 封装变化。

本篇文章介绍了三种工厂模式,工厂模式在实际应用中非常广泛,比如 Java 工具类中的 CalendarDateFormat 等。

(本节完。)

推荐阅读:

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

设计模式之高质量代码

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

码农充电站pro

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结