创建型模式抽象了实例化过程。它们帮助一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。
创建型模式有两个特点:
将具体的产品类的信息封装起来。
隐藏了产品类的实例是如何创建和组合的。
因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以及何时创建这些方面提供了极大的灵活性。
创建型模式之间的关系
有时创建型模式是相互竞争的。例如,在有些情况下,Abstract Factory和Prototype用起来都很好。
有时它们是互补的。例如,Builder可以使用其它模式去实现某个构件的构建。Prototype可以在它的实现中使用Singleton。
1 引入一个迷宫的示例
因为创建型模式紧密相关,我们通过一个通用的例子来研究它们的相似点和相异点。
为一个电脑游戏创建一个迷宫。这个迷宫将随着各种模式的不同而略有区别。
我们仅关注迷宫是如何被创建的。我们将迷宫定义为一系列房间,一个房间有四面;这四面要么是一堵墙,要么是到另一个房间的一扇门。
定义一个接口MapSite表示通用的迷宫组件,它只有一个操作enter(),表示你进入了什么——另一个房间或碰壁。
使用enum来定义房间的四面:东南西北。
实现MapSite接口的具体组件包括Room,Door和Wall三个类:
Room:保存一个房间号,并通过一个Map关联其四面对应的MapSite。
Door:关联两个房间。
Wall:墙壁对象。
注意:MapSite层次结构实际上可以看做一个Composite模式的实现,其中MapSite作为Component接口,Room作为Composite,Door和Wall作为Leaf。用户可以使用MapSite接口来一致地使用Room、Door和Wall对象。
package net.tequila.maze;
/**
* 通用的迷宫组件接口,作为Composite模式的Component。
*/
public interface MapSite {
/**
* 进入操作。
*
* 如果你进入一个房间,那么你的位置将发生改变。<br/>
* 当你试图进入一扇门,如果门是开着的,你进入另一个房间;如果门是关着的,那么你就会碰壁。
*/
void enter();
}
package net.tequila.maze;
/**
* 使用enmu来定义房间的四面:东南西北。
*/
public enum Direction {
NORTH, EAST, SOUTH, WEST
}
package net.tequila.maze;
import java.util.HashMap;
import java.util.Map;
/**
* 房间,作为Composite模式的Composite。
*
* 保存一个房间号,并通过一个<code>Map<Direction, MapSite></code>关联其四面对应的MapSite。
*/
public class Room implements MapSite, Cloneable {
private int roomNo;
private Map<Direction, MapSite> sides = new HashMap<Direction, MapSite>();
public Room() {
}
public Room(int roomNo) {
this.roomNo = roomNo;
}
public int getRoomNo() {
return roomNo;
}
public void setRoomNo(int roomNo) {
this.roomNo = roomNo;
}
public void setSides(Map<Direction, MapSite> sides) {
this.sides = sides;
}
public MapSite getSide(Direction d) {
return sides.get(d);
}
public void setSide(Direction d, MapSite mapSite) {
sides.put(d, mapSite);
}
@Override
public void enter() {
System.out.println("enter room " + roomNo);
}
// 重定义clone(),Prototype模式可以通过该方法克隆自身。
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
package net.tequila.maze;
/**
* 门,作为Composite模式的一个Leaf。
*
* 关联两个Room。
*/
public class Door implements MapSite, Cloneable {
private Room room1;
private Room room2;
private boolean isOpen;
public Door() {
}
public Door(Room room1, Room room2) {
this.room1 = room1;
this.room2 = room2;
}
public Room getRoom1() {
return room1;
}
public void setRoom1(Room room1) {
this.room1 = room1;
}
public Room getRoom2() {
return room2;
}
public void setRoom2(Room room2) {
this.room2 = room2;
}
@Override
public void enter() {
if (isOpen)
System.out.println("enter a door between room " + room1.getRoomNo()
+ " and room " + room2.getRoomNo());
else
System.out.println("the door is closed between room "
+ room1.getRoomNo() + " and room " + room2.getRoomNo());
}
public Room otherSideFrom(Room room) {
if (room == room1)
return room2;
else if (room == room2)
return room1;
else
return null;
}
// 重定义clone(),Prototype模式可以通过该方法克隆自身。
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
package net.tequila.maze;
/**
* 墙,作为Composite模式的一个Leaf。
*/
public class Wall implements MapSite, Cloneable {
@Override
public void enter() {
System.out.println("collide a wall...");
}
// 重定义clone(),Prototype模式可以通过该方法克隆自身。
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
然后,定义一个Maze类表示房间集合,通过房间号可以找到一个特定的房间。
房间号可以使用线性搜索、hash表、甚至一个简单数组进行一次查找。但这里不考虑这些细节,而是将注意力集中于如何指定一个迷宫对象的构件上。
package net.tequila.maze;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 迷宫:表示房间集合。
*/
public class Maze implements Cloneable {
private Map<Integer, Room> rooms = new HashMap<Integer, Room>();
public Collection<Room> getRooms() {
return rooms.values();
}
public void setRooms(Map<Integer, Room> rooms) {
this.rooms = rooms;
}
public Room getRoom(int roomNo) {
return rooms.get(roomNo);
}
public void addRoom(Room room) {
rooms.put(room.getRoomNo(), room);
}
// 重定义clone(),Prototype模式可以通过该方法克隆自身。
@Override
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return object;
}
}
最后,定义一个类MazeGame,由它来创建迷宫。一个简单直接的创建迷宫的方法是使用一系列操作将构件增加到迷宫中,然后连接它们。例如,下面的createMaze()创建一个简单迷宫,这个迷宫由两个房间和它们之间的一扇门组成。
package net.tequila.maze;
/**
* 负责创建迷宫。
*/
public class MazeGame {
/**
* 创建迷宫。这个迷宫由两个房间和它们之间的一扇门组成。
*
* 使用一系列操作将构件增加到迷宫中,然后连接它们。
*
* @return
*/
public Maze createMaze() {
Maze maze = new Maze();
Room room1 = new Room(1);
Room room2 = new Room(2);
maze.addRoom(room1);
maze.addRoom(room2);
Door door = new Door(room1, room2);
room1.setSide(Direction.NORTH, new Wall());
room1.setSide(Direction.EAST, door);
room1.setSide(Direction.SOUTH, new Wall());
room1.setSide(Direction.WEST, new Wall());
room2.setSide(Direction.NORTH, new Wall());
room2.setSide(Direction.EAST, new Wall());
room2.setSide(Direction.SOUTH, new Wall());
room2.setSide(Direction.WEST, door);
return maze;
}
}
创建迷宫的方法的问题在于它不够灵活。它对迷宫的布局进行硬编码;改变布局意味着改变这个方法,或是重定义它。这容易产生错误并且不利用重用。
2 引入模式来扩展迷宫
假如你想在迷宫游戏中重用一个已有的迷宫布局来创建新的迷宫,同时替换新的构件:例如,EnchantedRoom,一个可以有不寻常东西的房间,比如魔法钥匙或是咒语;DoorNeedingSpell,一扇仅随着一个咒语才能锁上和打开的门。你怎样才能较容易地改变createMaze()以让它用新类型的对象创建迷宫呢?
这种情况下,改变的最大障碍是对被实例化的类进行硬编码。创建型模式提供了多种不同方法从实例化它们的代码中除去对这些具体类的显式引用。
如果createMaze()调用抽象方法而不是构造器来创建它所需要的房间、门和墙壁,那么可以创建一个MazeGame的子类并重定义这些抽象方法,从而改变被实例化的类。这是Factory Method模式的一个例子。
如果传递一个对象给createMaze()做参数来创建房间、门和墙壁,那么可以传递不同的参数来改变房间、门和墙壁的类。这是Abstract Factory模式的一个例子,传递的参数是一个ConcreteFactory实例。
如果传递一个对象给createMaze(),这个对象可以在它所建造的迷宫中使用增加房间、门和墙壁的操作,来全面创建一个新的迷宫,那么可以使用继承来改变迷宫的一些部分或该迷宫被建造的方式。这是Builder模式的一个例子,传递的参数是一个ConcreteBuilder实例。
如果createMaze()由多种原型的房间、门和墙壁对象参数化,它拷贝并将这些对象增加到迷宫中,那么可以用不同的对象替换这些原型对象以改变迷宫的构成。这是Prototype模式的一个例子。
剩下的创建型模式,Singleton,可以保证每个游戏中仅有一个迷宫而且所有的游戏对象都可以迅速访问它。Singleton也使得迷宫易于扩展或替换,且不需变动已有的代码。
在应用不同的模式来改进这个例子之前,先扩展一下迷宫的组件,以便我们可以创建不同的迷宫组合。增加下列四个组件:
EnchantedRoom:施了魔法的房间。
DoorNeedingSpell:需要咒语的门。
RoomWithABomb:有一个炸弹的房间。
BombedWall:墙,可能由于炸弹爆炸而毁坏。
可以看出,EnchantedRoom和DoorNeedingSpell应该是关联的,两者用于构成一个魔法迷宫;而RoomWithABomb和BombedWall也是关联的,两者用于构成一个炸弹迷宫。
package net.tequila.maze;
/**
* 施了魔法的房间。
*/
public class EnchantedRoom extends Room {
/** 咒语 **/
private String spell;
public EnchantedRoom(int roomNo, String spell) {
super(roomNo);
this.spell = spell;
}
@Override
public void enter() {
System.out.println("enter enchanted room " + getRoomNo());
}
}
package net.tequila.maze;
/**
* 需要咒语的门。
*/
public class DoorNeedingSpell extends Door {
/** 咒语 **/
private String spell;
public DoorNeedingSpell(Room room1, Room room2, String spell) {
super(room1, room2);
this.spell = spell;
}
@Override
public void enter() {
System.out.println("enter a \"" + spell + "\" spell door between room "
+ getRoom1().getRoomNo() + " and room "
+ getRoom2().getRoomNo());
}
}
package net.tequila.maze;
/**
* 有一个炸弹的房间。
*/
public class RoomWithABomb extends Room {
public RoomWithABomb() {
super();
}
public RoomWithABomb(int roomNo) {
super(roomNo);
}
@Override
public void enter() {
System.out.println("enter room " + getRoomNo() + " with bomb");
}
}
package net.tequila.maze;
/**
* 墙,可能由于炸弹爆炸而毁坏。
*/
public class BombedWall extends Wall {
private boolean bomb;
public BombedWall() {
}
public BombedWall(boolean bomb) {
this.bomb = bomb;
}
public boolean isBomb() {
return bomb;
}
@Override
public void enter() {
if (bomb)
System.out.println("enter a bombed wall");
else
super.enter();
}
}
2.1 引入Factory Method模式
使用Factory Method模式来创建上面讨论的迷宫。
Factory Method模式的结构如下图:
下面简单介绍一下相关的类。
MazeGame:每一个工厂方法返回一个给定类型的迷宫构件,并提供一个缺省的实现。它作为Factory Method模式的Creator。
EnchantedMazeGame:重定义工厂方法,以实现一个魔法迷宫。它作为Factory Method模式的ConcreteCreator。
BombedMazeGame:重定义工厂方法,以实现一个炸弹迷宫。它作为Factory Method模式的ConcreteCreator。
package net.tequila.maze.factorymethod;
import net.tequila.maze.Direction;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.Wall;
/**
* 每一个工厂方法返回一个给定类型的迷宫构件,并提供一个缺省的实现。它作为Factory Method模式的Creator。
*/
public class MazeGame {
/**
* 创建迷宫。这个迷宫由两个房间和它们之间的一扇门组成。
*
* 使用工厂方法进行重写。
*
* @return
*/
public Maze createMaze() {
Maze maze = makeMaze();
Room room1 = makeRoom(1);
Room room2 = makeRoom(2);
maze.addRoom(room1);
maze.addRoom(room2);
Door door = makeDoor(room1, room2);
room1.setSide(Direction.NORTH, makeWall());
room1.setSide(Direction.EAST, door);
room1.setSide(Direction.SOUTH, makeWall());
room1.setSide(Direction.WEST, makeWall());
room2.setSide(Direction.NORTH, makeWall());
room2.setSide(Direction.EAST, makeWall());
room2.setSide(Direction.SOUTH, makeWall());
room2.setSide(Direction.WEST, door);
return maze;
}
// ================================================================
// 每一个工厂方法返回一个给定类型的迷宫构件,并提供缺省的实现。
// ================================================================
protected Maze makeMaze() {
return new Maze();
}
protected Room makeRoom(int roomNo) {
return new Room(roomNo);
}
protected Door makeDoor(Room room1, Room room2) {
return new Door(room1, room2);
}
protected Wall makeWall() {
return new Wall();
}
}
package net.tequila.maze.factorymethod;
import net.tequila.maze.Door;
import net.tequila.maze.DoorNeedingSpell;
import net.tequila.maze.EnchantedRoom;
import net.tequila.maze.Room;
/**
* 重定义工厂方法,以实现一个魔法迷宫。它作为Factory Method模式的ConcreteCreator。
*/
public class EnchantedMazeGame extends MazeGame {
@Override
protected Room makeRoom(int roomNo) {
return new EnchantedRoom(roomNo, castSpell());
}
@Override
protected Door makeDoor(Room room1, Room room2) {
return new DoorNeedingSpell(room1, room2, castSpell());
}
private String castSpell() {
return "magic";
}
}
package net.tequila.maze.factorymethod;
import net.tequila.maze.BombedWall;
import net.tequila.maze.Room;
import net.tequila.maze.RoomWithABomb;
import net.tequila.maze.Wall;
/**
* 重定义工厂方法,以实现一个炸弹迷宫。它作为Factory Method模式的ConcreteCreator。
*/
public class BombedMazeGame extends MazeGame {
@Override
protected Room makeRoom(int roomNo) {
return new RoomWithABomb(roomNo);
}
@Override
protected Wall makeWall() {
return new BombedWall(true);
}
}
2.2 引入Abstract Factory模式
使用Abstract Factory模式来创建上面讨论的迷宫。
Abstract Factory模式的结构如下图:
下面简单介绍一下相关的类。
MazeFactory:创建迷宫的组件,创建迷宫组件的方法使用Factory Method实现。MazeFactory仅是工厂方法的一个集合。 注意MazeFactory不是一个抽象类,因此它既作为AbstractFactory也作为ConcreteFactory。这是Abstract Factory模式的简单应用的另一个通常的实现。
EnchantedMazeFactory:创建魔法迷宫的组件,它作为Abstract Factory模式的ConcreteFactory。
BombedMazeFactory:创建炸弹迷宫的组件,它作为Abstract Factory模式的ConcreteFactory。
MazeGame:作为Abstract Factory模式的Client , crateMaze()根据不同的工厂来创建不同的迷宫。
package net.tequila.maze.abstractfactory;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.Wall;
/**
* 创建迷宫的组件,创建迷宫组件的方法使用Factory Method实现。
*
* MazeFactory仅是工厂方法的一个集合。 注意MazeFactory不是一个抽象类,因此它既作为Abstract
* Factory也作为ConcreteFactory。这是AbstractFactory模式的简单应用的另一个通常的实现。
*/
public class MazeFactory {
public Maze makeMaze() {
return new Maze();
}
public Room makeRoom(int roomNo) {
return new Room(roomNo);
}
public Door makeDoor(Room room1, Room room2) {
return new Door(room1, room2);
}
public Wall makeWall() {
return new Wall();
}
}
package net.tequila.maze.abstractfactory;
import net.tequila.maze.Door;
import net.tequila.maze.DoorNeedingSpell;
import net.tequila.maze.EnchantedRoom;
import net.tequila.maze.Room;
/**
* 创建魔法迷宫的组件,它作为Abstract Factory模式的ConcreteFactory。
*/
public class EnchantedMazeFactory extends MazeFactory {
@Override
public Room makeRoom(int roomNo) {
return new EnchantedRoom(roomNo, castSpell());
}
@Override
public Door makeDoor(Room room1, Room room2) {
return new DoorNeedingSpell(room1, room2, castSpell());
}
private String castSpell(){
return "magic";
}
}
package net.tequila.maze.abstractfactory;
import net.tequila.maze.BombedWall;
import net.tequila.maze.Room;
import net.tequila.maze.RoomWithABomb;
import net.tequila.maze.Wall;
/**
* 创建炸弹迷宫的组件,它作为Abstract Factory模式的ConcreteFactory。
*/
public class BombedMazeFactory extends MazeFactory {
@Override
public Room makeRoom(int roomNo) {
return new RoomWithABomb(roomNo);
}
@Override
public Wall makeWall() {
return new BombedWall(true);
}
}
package net.tequila.maze.abstractfactory;
import net.tequila.maze.Direction;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
/**
* 根据不同的工厂来创建不同的迷宫,作为Abstract Factory模式的Client。
*/
public class MazeGame {
/**
* 创建迷宫。
*
* 以MazeFactory作为参数来,方法中不再需要对类名进行硬编码。
*
* @param factory
* @return
*/
public Maze createMaze(MazeFactory factory) {
Maze maze = factory.makeMaze();
Room room1 = factory.makeRoom(1);
Room room2 = factory.makeRoom(2);
maze.addRoom(room1);
maze.addRoom(room2);
Door door = factory.makeDoor(room1, room2);
room1.setSide(Direction.NORTH, factory.makeWall());
room1.setSide(Direction.EAST, door);
room1.setSide(Direction.SOUTH, factory.makeWall());
room1.setSide(Direction.WEST, factory.makeWall());
room2.setSide(Direction.NORTH, factory.makeWall());
room2.setSide(Direction.EAST, factory.makeWall());
room2.setSide(Direction.SOUTH, factory.makeWall());
room2.setSide(Direction.WEST, door);
return maze;
}
}
2.3 引入Builder模式
使用Builder模式来创建上面讨论的迷宫。
Builder模式的结构如下图:
下面简单介绍一下相关的类。
MazeBuilder:定义创建迷宫组件的接口,它作为Builder模式的Builder。
StandardMazeBuilder:重定义创建迷宫组件的方法,它作为Builder模式的ConcreteBuilder。
MazeGame:根据不同的生成器,以一个统一的过程来构造不同的迷宫,它作为Builder模式的Director。
package net.tequila.maze.builder;
import net.tequila.maze.Maze;
/**
* 定义创建迷宫组件的接口,它作为Builder模式的Builder。
*
* 将构造产品部件的方法定义为空方法,便于子类只重定义它们感兴趣的方法。
*/
public abstract class MazeBuilder {
public abstract Maze getMaze();
public void buildMaze() {
}
/**
* 创建一个房间。
*
* @param roomNo
*/
public void buildRoom(int roomNo) {
}
/**
* 建造一扇两个房间之间的门。
*
* @param roomNo1
* @param roomNo2
*/
public void buildDoor(int roomNo1, int roomNo2) {
}
}
package net.tequila.maze.builder;
import net.tequila.maze.Direction;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.Wall;
/**
* 重定义创建迷宫组件的方法,它作为Builder模式的ConcreteBuilder。
*/
public class StandardMazeBuilder extends MazeBuilder {
private Maze maze;
public StandardMazeBuilder() {
maze = new Maze();
}
@Override
public Maze getMaze() {
return maze;
}
@Override
public void buildMaze() {
maze = new Maze();
}
/**
* 创建一个房间并建造它四周的墙壁。
*/
@Override
public void buildRoom(int roomNo) {
if (maze.getRoom(roomNo) == null) {
Room room = new Room(roomNo);
maze.addRoom(room);
room.setSide(Direction.NORTH, new Wall());
room.setSide(Direction.EAST, new Wall());
room.setSide(Direction.SOUTH, new Wall());
room.setSide(Direction.WEST, new Wall());
}
}
/**
* 建造一扇两个房间之间的门,同时查找这两个房间并找到它们之间的墙。
*/
@Override
public void buildDoor(int roomNo1, int roomNo2) {
Room room1 = maze.getRoom(roomNo1);
Room room2 = maze.getRoom(roomNo2);
Door door = new Door(room1, room2);
room1.setSide(commonWall(room1, room2), door);
room2.setSide(commonWall(room2, room1), door);
}
/**
* 功能示意方法,决定两个房间之间公共墙壁的方位。
*
* @param room1
* @param room2
* @return
*/
private Direction commonWall(Room room1, Room room2) {
for (Direction d : Direction.values()) {
if (room1.getSide(d) instanceof Door) {
return d;
}
}
for (Direction d : Direction.values()) {
if (room2.getSide(d) instanceof Door) {
if (d == Direction.NORTH)
return Direction.SOUTH;
else if (d == Direction.EAST)
return Direction.WEST;
else if (d == Direction.SOUTH)
return Direction.NORTH;
else
return Direction.EAST;
}
}
return Direction.EAST;
}
}
package net.tequila.maze.builder;
import net.tequila.maze.Maze;
/**
* 根据不同的生成器,以一个统一的过程来构造不同的迷宫,它作为Builder模式的Director。
*/
public class MazeGame {
/**
* 以MazeBuilder作为参数,方法中不再需要对类名进行硬编码。
*
* @param builder
* @return
*/
public Maze createMaze(MazeBuilder builder) {
builder.buildMaze();
builder.buildRoom(1);
builder.buildRoom(2);
builder.buildDoor(1, 2);
return builder.getMaze();
}
/**
* 重用MazeBuilder来创建不同种类的迷宫。
*
* @param builder
* @return
*/
public Maze createComplexMaze(MazeBuilder builder) {
builder.buildRoom(1);
// ...
builder.buildRoom(1001);
return builder.getMaze();
}
}
2.4 引入Prototype模式
Prototype模式的结构如下图:
定义MazeFactory的子类MazePrototypeFactory。该子类使用它要创建的对象的原型来初始化,这样我们就不需要仅仅为了改变它所创建的墙壁或房间的类而生成子类了。
MazePrototypeFactory:使用Prototype模式重定义工厂方法,它作为Abstract Factory的ConcreteFactory。
MazeGame:根据不同的工厂来创建不同的迷宫,作为Abstract Factory模式的Client。在Prototype实现中,作为参数的工厂,实际上是由不同原型集合初始化的MazePrototypeFactory实例。
package net.tequila.maze.prototype;
import java.util.HashMap;
import net.tequila.maze.Direction;
import net.tequila.maze.Door;
import net.tequila.maze.MapSite;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.Wall;
/**
* 使用Prototype模式重定义工厂方法,它作为Abstract Factory的ConcreteFactory。
*
* 注意:这里的克隆使用浅度克隆。
*/
public class MazePrototypeFactory extends MazeFactory {
private Maze prototypeMaze;
private Room prototypeRoom;
private Door prototypeDoor;
private Wall prototypeWall;
public MazePrototypeFactory(Maze maze, Room room, Door door, Wall wall) {
this.prototypeMaze = maze;
this.prototypeRoom = room;
this.prototypeDoor = door;
this.prototypeWall = wall;
}
@Override
public Maze makeMaze() {
Maze maze = (Maze) prototypeMaze.clone();
maze.setRooms(new HashMap<Integer, Room>());
return maze;
}
@Override
public Room makeRoom(int roomNo) {
Room room = (Room) prototypeRoom.clone();
room.setRoomNo(roomNo);
room.setSides(new HashMap<Direction, MapSite>());
return room;
}
@Override
public Door makeDoor(Room room1, Room room2) {
Door door = (Door) prototypeDoor.clone();
door.setRoom1(room1);
door.setRoom2(room2);
return door;
}
@Override
public Wall makeWall() {
Wall wall = (Wall) prototypeWall.clone();
return wall;
}
}
package net.tequila.maze.prototype;
import net.tequila.maze.BombedWall;
import net.tequila.maze.Direction;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.RoomWithABomb;
import net.tequila.maze.Wall;
/**
* 根据不同的工厂来创建不同的迷宫,作为Abstract Factory模式的Client。
*/
public class MazeGame {
/**
* 以MazeFactory作为参数,方法中不再需要对类名进行硬编码。
*
* @param factory
* @return
*/
public Maze createMaze(MazeFactory factory) {
Maze maze = factory.makeMaze();
Room room1 = factory.makeRoom(1);
Room room2 = factory.makeRoom(2);
maze.addRoom(room1);
maze.addRoom(room2);
Door door = factory.makeDoor(room1, room2);
room1.setSide(Direction.NORTH, factory.makeWall());
room1.setSide(Direction.EAST, door);
room1.setSide(Direction.SOUTH, factory.makeWall());
room1.setSide(Direction.WEST, factory.makeWall());
room2.setSide(Direction.NORTH, factory.makeWall());
room2.setSide(Direction.EAST, factory.makeWall());
room2.setSide(Direction.SOUTH, factory.makeWall());
room2.setSide(Direction.WEST, door);
return maze;
}
// 使用基本迷宫构件的原型初始化MazePrototypeFactory,创建一个原型的或缺省的迷宫。
public MazePrototypeFactory simpleMazeFactory() {
return new MazePrototypeFactory(new Maze(), new Room(), new Door(),
new Wall());
}
// 使用爆炸主题的原型集合初始化MazePrototypeFactory,创建一个爆炸迷宫。
public MazePrototypeFactory bombedMazeFactory() {
return new MazePrototypeFactory(new Maze(), new RoomWithABomb(),
new Door(), new BombedWall(true));
}
}
2.4 引入Singleton模式
Singleton模式的结构如下图:
假定定义一个MazeFactory用于建造上面讨论的迷宫,Maze应用应该仅需要一个迷宫工厂的实例,且这个实例对建造迷宫任何部件的代码都是可用的。所以将MazeFactory作为单件。
由于MazeFactory存在多个子类,所以客户端通过配置(如环境变量、配置文件等)来决定使用哪一个子类。
package net.tequila.maze.singleton;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import net.tequila.maze.Door;
import net.tequila.maze.Maze;
import net.tequila.maze.Room;
import net.tequila.maze.Wall;
/**
* 创建迷宫的组件。这里引入Singleton模式,将MazeFactory作为单件。
*
* MazeFactory仅是工厂方法的一个集合。 注意MazeFactory不是一个抽象类,因此它既作为Abstract
* Factory也作为ConcreteFactory。这是AbstractFactory模式的简单应用的另一个通常的实现。
*/
public class MazeFactory {
private static MazeFactory instance;
/**
* 通过配置(如环境变量、配置文件等)选择迷宫的种类,并增加代码用于实例化适当的MazeFactory子类。
* 缺点在于只要新增了一个MazeFactory的子类,都必须修改getInstance()。
*/
public static MazeFactory getInstance() {
if (instance == null) {
Properties props = new Properties();
InputStream in = null;
try {
in = MazeFactory.class.getResourceAsStream("maze.properties");
props.load(in);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
String mazeStyle = props.getProperty("mazeStyle");
if ("enchanted".equals(mazeStyle))
instance = new EnchantedMazeFactory();
else if ("bombed".equals(mazeStyle))
instance = new BombedMazeFactory();
else
instance = new MazeFactory();
}
return instance;
}
protected MazeFactory() {
}
public Maze makeMaze() {
return new Maze();
}
public Room makeRoom(int roomNo) {
return new Room(roomNo);
}
public Door makeDoor(Room room1, Room room2) {
return new Door(room1, room2);
}
public Wall makeWall() {
return new Wall();
}
}
3 创建型模式的讨论
用一个系统创建创建的那些对象对系统进行参数化有两种常用方法。
(1)生成创建对象的类的子类:对应于使用Factory Method模式。
缺点在于,仅为了改变产品类,就可能需要创建一个新的子类。这样的改变可能是级联的。例如,如果产品的创建者本身是由一个工厂方法创建的,那么你也必须重定义它的创建者。
(2)依赖于对象复合:定义一个对象负责明确产品对象的类,并将它作为该系统的参数。这是Abstract Factory、Builder和Prototype模式的关键特征。
Abstract Factory由这个工厂对象产生多个类的对象。
Builder由这个工厂对象使用一个相对复杂的协议,逐步创建一个复杂产品。
Prototype由该工厂对象通过拷贝原型对象来创建产品对象。在这种情况下,因为原型负责返回产品对象,所以工厂对象和原型是同一个对象。
再回过头来看前面的迷宫示例,可以有多种方法通过产品类来参数化MazeGame。
使用Factory Method模式,将为每一个种类的迷宫创建一个MazeGame的子类(普通迷宫对应MazeGame,魔法迷宫对应EnchantedMazeGame,炸弹迷宫对应BombedMazeGame), MazeGame定义了创建不同迷宫构件的方法(包括房间、门和墙壁等),并提供了一个缺省的实现,每个MazeGame的子类都会重定义它感兴趣的方法。
使用Abstract Factory模式,将有一个MazeFactory类层次对应于每一个种类的迷宫, 在这种情况下每一个工厂创建一个特定的迷宫,MazeFactory将创建普通迷宫,EnchantedMazeFactory将创建魔法迷宫,BombedMazeFactory将创建炸弹迷宫对应,等等。MazeGame将以创建合适种类的迷宫的工厂作为参数。
使用Prototype模式,每个MapSite的子类将实现clone操作,并且MazePrototypeFactory将以它所创建的MapSite的原型来初始化。
究竟哪一种模式最好取决于诸多因素。
Factory Method使一个设计可以定制且只略微有一些复杂。使用Abstract Factory、Builder和Prototype的设计设置比使用Factory Method的设计更灵活(比如在初始化操作中),但它们也更加复杂。
通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性时,设计便向其它创建型模式演化。
下面再对创建型模式的实现做一个比较。
简单工厂是工厂方法的一种特例。
工厂方法延迟到子类来选择实现。如果直接在工厂类里选择实现,就退化成简单工厂了。
抽象工厂通常使用工厂方法来实现,当然也可以使用原型。
抽象工厂可以退化为工厂方法。
工厂方法模式关注的是单个产品的创建;虽然工厂类中可以有多个工厂方法用于创建多个对象,但是这些对象之间一般是没有联系的。抽象工厂模式关注的是一系列产品对象的创建,而且这一系列对象是构建新的对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的。如果抽象工厂中只定义一个方法,直接创建产品,那么就退化成为工厂方法了。
生成器需要创建产品部件的实例,可以使用工厂方法或原型来得到部件的实例。
生成器和抽象工厂的比较。
生成器负责创建产品部件,并将这个部件对象装配到产品对象中。可以理解为,生成器和工厂方法配合使用。再进一步,如果在实现生成器的时候,只有创建产品部件的功能,而没有组装的功能,那么生成器实现和抽象工厂的实现是类似的。在这种情况下,Builder接口类似于抽象工厂的接口,Builder的具体实现类类似于具体工厂,而且Builder接口里面定义的创建各个部件的方法也是有关联的,它们负责构建一个复杂对象所需要的部件对象。
来源:oschina
链接:https://my.oschina.net/u/197334/blog/602029