Java建立型模式的討論

        建立型模式抽象了實例化過程。它們幫助一個系統獨立於如何建立、組合和表示它的那些對象。一個類建立型模式使用繼承改變被實例化的類,而一個對象建立型模式將實例化委託給另外一個對象。java

建立型模式有兩個特色:數組

  • 將具體的產品類的信息封裝起來。ide

  • 隱藏了產品類的實例是如何建立和組合的。佈局

        所以,建立型模式在什麼被建立,誰建立它,它是怎樣被建立的,以及什麼時候建立這些方面提供了極大的靈活性。ui


        建立型模式之間的關係this

  • 有時建立型模式是相互競爭的。例如,在有些狀況下,Abstract Factory和Prototype用起來都很好。編碼

  • 有時它們是互補的。例如,Builder可使用其它模式去實現某個構件的構建。Prototype能夠在它的實現中使用Singleton。spa

1 引入一個迷宮的示例

        由於建立型模式緊密相關,咱們經過一個通用的例子來研究它們的類似點和相異點。prototype

        爲一個電腦遊戲建立一個迷宮。這個迷宮將隨着各類模式的不一樣而略有區別。設計

        咱們僅關注迷宮是如何被建立的。咱們將迷宮定義爲一系列房間,一個房間有四面;這四面要麼是一堵牆,要麼是到另外一個房間的一扇門。


        定義一個接口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接口裏面定義的建立各個部件的方法也是有關聯的,它們負責構建一個複雜對象所須要的部件對象。

相關文章
相關標籤/搜索