注:所有知识来源于《设计模式:可复用软件面向对象的基础》
创建型设计模式抽象了实例化过程,它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。
在这些模式中有两个不断出现的主旋律:
- 它们都将关于该系统使用哪些具体的类的信息封装起来。
- 它们隐藏了这些类的实例是如何被创建和放在一起的。
整个系统关于这些对象所知道的是由抽象类所定义的接口。
示例—创建迷宫
我们以为游戏创建一个迷宫作为学习创建型模式的例子,忽略迷宫中的许多细节以及一个迷宫游戏中有多少游戏者,仅关注迷宫是怎么创建的。我们将一个迷宫定义为一系列房间,一个房间知道它的邻居:可能的邻居要么是另一个房间,要么是一堵墙或者是到另一个房间的一扇门。
在该例子中,需要使用到的作为迷宫构件的类有Room、Door和Wall,注意我们只关注怎样创建迷宫,忽略游戏者、显示操作和在迷宫中四处移动等操作,以及其他一些重要的却与创建迷宫无关的功能。
图1 迷宫构件的类图
- 每一个Room有四面,用枚举类Direction来表示。
public enum Direction { NORTH, SOUTH, EAST, West}
- 类MapSite是所有迷宫构件的抽象类,其中只定义了一个操作enter(),其含义取决于进入哪里:如果进入一个房间,那么位置发生改变;如果进入一扇门,若门开着则进入另一房间且位置改变,若门关着则碰壁且位置不变。
public abstract class MapSite { public abstract void enter();}
- 类Room是MapSite的一个具体子类,而MapSite定义了迷宫中构件之间的主要关系,Room有指向其他MapSite对象的引用,并保存一个房间号用来表示迷宫种的房间,sites是指当前房间四个方向所相邻的构件。
public class Room extends MapSite { private int roomNumber; private Map sites; public Room(int roomNumber) { this.roomNumber = roomNumber; this.sites = new HashMap(4); } public MapSite getSide(Direction direction) { return sites.get(direction.getDirection()); } public void setSites(Direction direction, MapSite mapSite) { sites.put(direction.getDirection(), mapSite); } public int getRoomNumber() { return this.roomNumber; } @Override public void enter() { System.out.println("Enter another room and position change."); }}
- 类Wall是可能出现在房间四面的墙。
public class Wall extends MapSite { @Override public void enter() { System.out.println("It's a wall. Position not change."); }}
- 类Door是可能出现在房间四面的门,它连接了两个房间,同时它本身可能是开着的也可能是关着的。
public class Door extends MapSite { private Room room1; private Room room2; boolean isOpen; public Door(Room room1, Room room2, boolean isOpen) { this.room1 = room1; this.room2 = room2; this.isOpen = isOpen; } @Override public void enter() { if (isOpen) { System.out.println("The door is opened. Enter another room and position change."); } else { System.out.println("The door is closed. Position not change."); } }}
- 有了迷宫的各个构件后就可以组成迷宫了,类Maze就是迷宫,迷宫是由各个房间组成的,通过getRoom操作和给定的房间号,Maze就能找到特定的房间,此处采用HashMap类保存房间集合。
public class Maze { Map rooms = new HashMap(); public void addRoom(Room room) { rooms.put(room.getRoomNumber(), room); } public Room getRoom(int roomNumber) { return rooms.get(roomNumber); }}
- 当我们想构建一个只有两个迷宫且这两个迷宫是通过一扇门来连接的时候,其创建操作如下:
public class MazeGame { public Maze createMaze() { Maze maze = new Maze(); Room room1 = new Room(1); Room room2 = new Room(2); Door door = new Door(room1, room2, true); maze.addRoom(room1); maze.addRoom(room2); room1.setSites(Direction.NORTH, new Wall()); room1.setSites(Direction.SOUTH, new Wall()); room1.setSites(Direction.EAST, door); room1.setSites(Direction.West, new Wall()); room2.setSites(Direction.NORTH, new Wall()); room2.setSites(Direction.SOUTH, new Wall()); room2.setSites(Direction.EAST, new Wall()); room2.setSites(Direction.West, door); return maze; }}
考虑到这创建的是一个只有两个房间的迷宫就已经显得相当复杂了,虽然可以提前在Room构造器里用墙壁初始化房间的每一面,但是这只是把代码移到了其它地方。这个方法真正的问题不在于它的大小而在于它不灵活,它对迷宫的布局进行硬编码。改变布局意味着改变这个成员方法,通过以下方式定义:重定义(override)它——意味着重新实现整个过程;对它的部分进行改变——容易产生错误并且不利于复用。
这种情况下改变的最大障碍是对以实例化的类进行硬编码,创建型模式就提供了多种不同的方法,从实例化它们的代码中去除对这些具体类的显示引用。
- 如果createMaze调用抽象方法或接口而不是构造器来创建它需要的房间、墙壁和门,那么可以创建一个MazeGame的子类并重写这些方法或接口,从而改变被实例化的类。这一方法是Factory Method模式的使用。
- 如果传递一个对象给createMaze作为参数来创建房间、墙壁和门,那么可以传递不同的参数来改变房间、墙壁和门的类。这是Abstract Factory模式的使用。
- 如果传递一个对象给createMaze,这个对象可以在它所建造的迷宫中使用增加房间、墙壁和门的操作来全面创建一个新的迷宫,那么可以使用继承来改变迷宫的一些部分或迷宫的建造方式。这是Builder模式的使用。
- 如果createMaze由多种原型的房间、墙壁和门对象参数化,它复制并将这些对象增加到迷宫中,那么可以用不同的对象替换这些原型对象以改变迷宫的构成。这是Prorotype模式的使用。
剩下的Singleton模式可以保证每个游戏中仅有一个迷宫而且所有的游戏对象都可以迅速访问它——不需要求助于全局变量或方法。Singleton也使得迷宫易于扩展或替换且不需要变动已有的代码。