这是汇总设计模式书上的内容。但是书上每种模式使用单独的例子,有时候会比较难以理解。这里用同一个例子来汇总它们,这样相对而言会比较好理解一些。
创建模式的两个特点:
因为这两个特点,创建模式在
四个方面提供了灵活性。
下面是一个例子:
假设我们需要创建一个迷宫游戏(MazeGame),这个迷宫游戏会创建一个迷宫(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;
}
}
这是仅有两个房间的迷宫,它的问题硬编码,比如需要修改墙壁/门的实现(比如有的墙壁砸碎,有的门用密码锁,有的门用钥匙),那么就需要重新改这段代码。
不同的创建型模式使用不同的方法来解决硬编码这个问题。具体如下:
还有 Singleton 则可以不借助全局变量的情况下,确保应用中只有一个 Maze,且所有组件都可以访问这个 Maze。
客户端只跟抽象的工厂、抽象的产品进行交互。客户端通过抽象的工厂创建 ”抽象“ 的产品。这样就可屏蔽客户端与具体的产品的耦合。切换抽象工厂实现,即可切换客户端使用的产品系列。
我们假设上面的迷宫游戏在进入游戏之前有可以选择生成不同的迷宫:
用户进入游戏时,可以选择生成什么样的迷宫。这时,就不能再生成迷宫时硬编码迷宫组件了。这时,合适使用抽象工厂的方式来进行实例化迷宫。使用抽象工厂,我们需要给每个组件设计一个抽象类,然后为抽象组件编写两套实现(正产组件,魔法组件)。工厂也需要一个抽象工厂,然后同样为抽象工厂写两种实现。之后客户端只跟抽象工厂,抽象组件进行交互。用户选择不同的迷宫,客户端通过抽象工厂使用对应的工厂实现,然后实例化对应的迷宫组件。抽象工厂组件的关系如下图所示:
抽象工厂有一个缺陷,即不能增加新的产品(组件)。比如迷宫游戏需要新增一个组件 – 窗户。那么就需要修改抽象工厂的所有实现。
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 模式的架构图如下所示:
注意,上图的虚线框表示 Client 直接创建具体Builder,它不使用抽象Builder。它构造完传给 Director,Director通过抽象 Builder 接收。他们和客户端交互的结构如上面例子所示(MazeGame即客户端,它实例化 Director 和具体Builder)
工厂方法比较简单,只是创建一个抽象类(接口),然后使用子类来创建具体产品,这样不同的子类就可以创建不同的产品了。
比如上面的例子,需要在用户登录时候,选择创建魔法迷宫还是正常迷宫。这时候可以使用一个抽象的 MazeGame,它的子类继承它,然后实现具体 createMaze 。当用户选择正常迷宫时,子类 NormalMazeGame 就创建一个 NormalMaze,当用户选择魔法迷宫时,子类 MagicMazeGame 就创建一个 MagicMaze。它们的结构如下:
原型方法是指,使用一些小的类实例作为原型,其他大的类实例由原型实例复制后,组装而成。
原型模式可以解决如下问题:
下面是迷宫例子使用原型模式的结构图
Singleton 模式用于确保类的实例最多有一个。单实例模式常见的实现是:
有时也可以不用 getInstance() 方法,并公开类成员 instance 同时给它实例化一个实例。但是这是在加载类的时候进行实例化,它有一个问题就是即使这个实例不用也会被初始化。而多了 getInstance() 方法后,不仅可以延迟初始化到使用时,还可以初始化一个子类给它(或者从注册表中选一个子类给 instance)。总体而言,更加灵活。
创建类实例的方法大致可以分成两种
再次将第一段不同创建模式创建 MazeGame 的概要复制如下: