四种创建型设计模式的含义理解和对比(抽象工厂模式+建造者模式+工厂方法模式+原型模式+单例模式)

创建模式

这是汇总设计模式书上的内容。但是书上每种模式使用单独的例子,有时候会比较难以理解。这里用同一个例子来汇总它们,这样相对而言会比较好理解一些。

创建模式的两个特点:

  1. 隐藏系统中的类
  2. 隐藏这些类实例的创建过程(组装方式)

因为这两个特点,创建模式在

  1. 谁创造
  2. 创造谁
  3. 怎么创造
  4. 什么时候创造

四个方面提供了灵活性。

下面是一个例子:

假设我们需要创建一个迷宫游戏(MazeGame),这个迷宫游戏会创建一个迷宫(Maze),这时候创建模式就可以起作用了。

假设构建迷宫需要用到如下的组件:

  1. Wall – 墙壁
  2. Door – 门,链接两个房间
  3. Room – 房间,有四个方向(东南西北),每个方向有门/墙
  4. Maze – 迷宫,有多个房间

那么构建迷宫的一般思路是:

public class MazeGame{
    public Maze createMaze(){
        Maze maze = new Maze();
        Room r1 = new Room();
        Room r2 = new Room();
        Door d = new Door();
        
        maze.add(r1);
        maze.add(r2);
        
        d.connect(r1, r2);
        
        r1.setSide(Nort, new Wall());
        r1.setSide(East, d);
        r1.setSide(South, new Wall());
        r1.setSide(West, new Wall());
        
        r1.setSide(Nort, new Wall());
        r1.setSide(East, new Wall());
        r1.setSide(South, new Wall());
        r1.setSide(West, d);
        
        return maze;
    }
}

这是仅有两个房间的迷宫,它的问题硬编码,比如需要修改墙壁/门的实现(比如有的墙壁砸碎,有的门用密码锁,有的门用钥匙),那么就需要重新改这段代码。

不同的创建型模式使用不同的方法来解决硬编码这个问题。具体如下:

  1. Factory Method: 给 MazeGame 新增一个抽象函数,getComponent, 通过它来获取 Door,Wall,Room而不是直接new它们。这样不同的 MazeGame 子类可以实现不同的 getComponent,就可以实例化新的子类了,比如使魔法咒语才能打开的门。
  2. Abstract Factory: 给MazeGame传递一个对象,用它来创建 Room,Wall,Door。那么根据传递的不同对象,就可以创建不同Room,Wall,Door
  3. Builder: 给MazeGame传递一个对象,它可以新建一个迷宫,并给迷宫增加 Room,Wall,Door 等组件。那么可以通过继承 Builder 来创建不同的对象,比如使用魔法咒语才能打开的门。
  4. Prototype: 如果 Room,Wall,Door 是由它们自己的原型拷贝而生成。那么可以通过替换原型对象来生成不同的对象,比如魔法咒语才能打开的门。

还有 Singleton 则可以不借助全局变量的情况下,确保应用中只有一个 Maze,且所有组件都可以访问这个 Maze。

1. 抽象工厂

客户端只跟抽象的工厂、抽象的产品进行交互。客户端通过抽象的工厂创建 ”抽象“ 的产品。这样就可屏蔽客户端与具体的产品的耦合。切换抽象工厂实现,即可切换客户端使用的产品系列。

我们假设上面的迷宫游戏在进入游戏之前有可以选择生成不同的迷宫:

  1. 正常迷宫,比如使用钥匙开门
  2. 魔法迷宫,比如使用咒语开门,其他组件同样跟正常迷宫有所差异。

用户进入游戏时,可以选择生成什么样的迷宫。这时,就不能再生成迷宫时硬编码迷宫组件了。这时,合适使用抽象工厂的方式来进行实例化迷宫。使用抽象工厂,我们需要给每个组件设计一个抽象类,然后为抽象组件编写两套实现(正产组件,魔法组件)。工厂也需要一个抽象工厂,然后同样为抽象工厂写两种实现。之后客户端只跟抽象工厂,抽象组件进行交互。用户选择不同的迷宫,客户端通过抽象工厂使用对应的工厂实现,然后实例化对应的迷宫组件。抽象工厂组件的关系如下图所示:

四种创建型设计模式的含义理解和对比(抽象工厂模式+建造者模式+工厂方法模式+原型模式+单例模式)_第1张图片

抽象工厂有一个缺陷,即不能增加新的产品(组件)。比如迷宫游戏需要新增一个组件 – 窗户。那么就需要修改抽象工厂的所有实现。

2.Builder

Builder模式用于将构建过程和对象表示进行分离。可以使得同样的构建过程产生不同的对象(或者对象表示)。(对象的表示,即对象的组件的组装方式)

使用上面的例子,比如我们需要在进入游戏的可以选择生成正常的迷宫或生成魔法迷宫。但是我们想要这个构建的过程是一样,只是他们能构建出拥有不同组件的对象。比如:不管我想要构建的正常还是魔法迷宫,都是两个2房间,都是第1个房间的东面和第二房间的西面是门,其余都是墙壁。我们可以将这个构建过程固定下来,比如:

public class MazeDirector(){
	public void create(Builder builder){
        builder.addRoom(1);
        builder.addRoom(2);
        builder.setDoor(1, 2, East, West);
    }
}

public class MazeGame{
    public Maze createMaze(){
        Builder b = new NormalMazeBuilder();
        MazeDirector d = new MazeDirector();
        d.create(b);
        return b.build();
    }
}

这样,不管是魔法迷宫还是正常迷宫,他们的构建过程就一样了。通过传入不同的 Builder 实现来获取一个正常的迷宫还是一个魔法迷宫。这里固定构建过程的 MazeDirector 在 Builder 模式中称为 Directory。综上所述,Builder 模式的架构图如下所示:

四种创建型设计模式的含义理解和对比(抽象工厂模式+建造者模式+工厂方法模式+原型模式+单例模式)_第2张图片

注意,上图的虚线框表示 Client 直接创建具体Builder,它不使用抽象Builder。它构造完传给 Director,Director通过抽象 Builder 接收。他们和客户端交互的结构如上面例子所示(MazeGame即客户端,它实例化 Director 和具体Builder)

3. Factory Method

工厂方法比较简单,只是创建一个抽象类(接口),然后使用子类来创建具体产品,这样不同的子类就可以创建不同的产品了。

比如上面的例子,需要在用户登录时候,选择创建魔法迷宫还是正常迷宫。这时候可以使用一个抽象的 MazeGame,它的子类继承它,然后实现具体 createMaze 。当用户选择正常迷宫时,子类 NormalMazeGame 就创建一个 NormalMaze,当用户选择魔法迷宫时,子类 MagicMazeGame 就创建一个 MagicMaze。它们的结构如下:

四种创建型设计模式的含义理解和对比(抽象工厂模式+建造者模式+工厂方法模式+原型模式+单例模式)_第3张图片

4. Prototype

原型方法是指,使用一些小的类实例作为原型,其他大的类实例由原型实例复制后,组装而成。

原型模式可以解决如下问题:

  • 当待实例化的类是动态加载时,需要用原型模式。用上面的例子即,我们书写程序程序的时候,只有抽象的Wall,Door,Room 这些结构。运行时具体 NormalWall,NormalDoor,NormalRoom 或是 MagicWall,MagicDoor,MagicRoom 是有另外两个小组的同事提供的,目前还没有。所以我们可以给抽象 Wall,Door,Room 设置一个 clone 方法。让子类实现它,这样我们运行时,可以通过 clone 方法获取具体子类的实例,而不需要指定它名字(或者包)。
  • 当一个组装结构经常被使用时,可以把建立这个组装结构的原型,以后要用同样的结构时,就直接复制,而不是每次新组装。用上面的例子就是,比如迷宫中有100个房间,其中东西南面是墙,北面是门,这种结构的房间用的很多。就可以单独为它建立一个原型。以后需要用到这个结构,直接复制原型,而不用每次组装。

下面是迷宫例子使用原型模式的结构图

四种创建型设计模式的含义理解和对比(抽象工厂模式+建造者模式+工厂方法模式+原型模式+单例模式)_第4张图片

5.Singleton

Singleton 模式用于确保类的实例最多有一个。单实例模式常见的实现是:

  • 一个 protected 的构造函数 – 避免被外部实例化
  • 一个私有类成员 instance – 用于存储那个实例
  • 一个公有类方法 getInstance() – 用于实例化并返回单实例

有时也可以不用 getInstance() 方法,并公开类成员 instance 同时给它实例化一个实例。但是这是在加载类的时候进行实例化,它有一个问题就是即使这个实例不用也会被初始化。而多了 getInstance() 方法后,不仅可以延迟初始化到使用时,还可以初始化一个子类给它(或者从注册表中选一个子类给 instance)。总体而言,更加灵活。

6.创建型模式汇总

创建类实例的方法大致可以分成两种

  • 创建一个抽象类,再创建它的子类,每一个子类创建一种产品实例。它对应 Factory Method
  • 创建一个类,将它作为系统参数,通过参数创建产品,对应有:Abstract Factory,Builder,Prototype。

再次将第一段不同创建模式创建 MazeGame 的概要复制如下:

  1. Factory Method: 给 MazeGame 新增一个抽象函数,getComponent, 通过它来获取 Door,Wall,Room而不是直接new它们。这样不同的 MazeGame 子类可以实现不同的 getComponent,就可以实例化新的子类了,比如使魔法咒语才能打开的门。
  2. Abstract Factory: 给 MazeGame 传递一个对象,用它来创建 Room,Wall,Door。那么根据传递的不同对象,就可以创建不同Room,Wall,Door
  3. Builder: 给 MazeGame 传递一个对象,它可以新建一个迷宫,并给迷宫增加 Room,Wall,Door 等组件。那么可以通过继承 Builder 来创建不同的对象,比如使用魔法咒语才能打开的门。
  4. Prototype: 如果 Room,Wall,Door 是由它们自己的原型拷贝而生成。那么可以通过替换原型对象来生成不同的对象,比如魔法咒语才能打开的门。

你可能感兴趣的:(java,设计模式,抽象工厂模式,工厂方法模式,建造者模式,原型模式)