目的
爲創建相關對象或者互相依賴的對象提供一個接口,並且不指出他們的實現類。
動機
考慮一個ui的工具箱,使它能夠支持多種風格標準,例如 Motif 和 Presentation Manager.不同的風格標準爲ui組件如滾動條,窗口,按鈕 定義了不同的展示和行爲。爲了使程序可以在不同的風格標準中自由切換,應用程序不應該爲了實現某種外觀而進行硬編碼。在應用程序中實例化特定的風格標準組件類使得我們後面很難改變風格。
我們可以通過定義一個抽象窗口工廠類來解決這個問題。這個類定義了創建基本窗口組件類型的接口。每一個窗口組件都會定義一個抽象類,窗口組件的子類實現了不同的風格標準。對於每一個抽象的窗口組件類,窗口工廠都包括一個返回該類對應的具體類的窗口對象的操作。客戶端的代碼調用這些操作來獲得窗口實例,但是客戶端代碼不知道具體的子類。所以客戶端代碼和一般的的風格解耦了。
應用場景
1 系統和他的產品的創建,組合,展示保持獨立。
2 系統需要和一系列的產品進行配置。
3 相關的產品被設計成需要一起使用,而你需要強化這個限制。
4 你想提供一個產品的類庫,而且你只是想透露他們的接口,而不想透露他們的實現。
結構
參與者
AbstractFactory :聲明創建抽象產品對象的接口。
ConcreteFactory:實現創建具體產品對象的接口。
AbstractProduct:聲明一類產品對象
ConcreteProduct:定義一個被相應的具體工廠對象創建的產品對象
並且實現抽象產品的接口
Client:只適用抽象工廠和抽象產品定義的接口。
合作:
一般的,一個具體工廠類對象會在運行時創建。這個具體工廠類創建擁有特別實現的產品對象。爲了創建不同的產品對象,客戶應該使用不同的具體工廠類。
抽象工廠類把產品對象的創建推遲到他的具體子類中。
影響
抽象工廠設計模式具有如下優點和限制。
1 他隔離了具體子類。抽象工廠模式幫助你控制應用程序創建的對象相應的類。因爲一個工廠封裝了創建產品對象的職責和過程,它使得客戶端代碼和具體的實現類隔離。客戶代碼通過他們的抽象接口來管理代碼。產品類的名字被隔離在具體工廠的實現中。他們並不在客戶代碼中出現。
2 它使得改變產品家族變得更加容易。具體工廠類只在客戶代碼中出現一次----就是在第一次初始化的時候出現。這使得我們很容易改變應用程序使用的具體工廠類。如果你想使用不同的產品配置,只要簡單的改變一下具體工廠就可以了。因爲一個抽象的工程創建了一個完整的產品家族,整個產品家族一起改變。在我們得ui例子中,我們只要切換相應的工廠對象就可以把Motif風格切換到PM風格。
3 他提倡產品的一致性。當一個產品家族的產品對象被設計成在一起工作,應用程序一次使用一個產品家族是很重要的。抽象工廠使得這一點很容易達到強化。
4 支持新的產品類型比較困難。擴展新的抽象工廠去創建新的產品種類不是很簡單。這是因爲抽象工廠的接口固定了它可以創建的產品集。支持新的產品種類,需要擴展工廠接口,這個涉及改變抽象工廠類和他的所有子類。我們會在接下來的實現章節中討論這個問題的一種解決方法。
實現
這裏介紹一下實現抽象工廠模式有用的一些技術。
1 單例模式實現工廠。應用程序在創建每個家族產品時只需一個工廠實例。所以,通常將抽象工廠實現爲單例形式。
2 創建產品。抽象工廠只是聲明瞭創建產品的接口。創建具體的產品的職責由具體的子類工廠來創建。最通常的方式是使用工廠函數來創建每一個產品。一個具體的工廠子類通過重寫工廠函數來會指定它的產品。雖然這個實現比較簡單,但是如果家族產品有一些輕微的差別,它要求一個新的工廠子類來創建每一個家族產品。
如果很多產品家族需要生成,具體的工廠子類可以用原型(prototype)來實現。具體工廠類由家族產品中的每個產品的原型實例初始化。它通過克隆它的原型來創建產品。這種基於原型的方法消除了針對每個新家族產品需要創建一個新的具體工廠子類的需要。
3 定義可擴展的工廠。抽象工廠模式經常爲每個它能夠生產的產品種類定義一個不同的操作。這些產品在操作代碼段中寫死了。增加一個新產品需要改變抽象工廠的接口和所有依賴它的類。一個更加靈活但是稍微不安全的方法就是給這個創建對象的操作加上一個參數。這個參數指定了需要創建對象的是屬於哪種類型的。它可以是用類來標示,或者是整數,字符串,或者任何可以標示這個產品種類的。事實上,如果使用這種方法,抽象工廠只需要使用一個含可以標示需要創建的對象的種類的參數的操作函數就可以了。這個技術在原型模式或者在之前談論過的基於類的抽象工廠模式中會使用。
例子代碼
我們會把抽象工廠模式應用到之前我們談論過的迷宮的創建
類 MazeFactory 能夠創建迷宮的很多組件。它會去創建房間,牆壁,和房間中間的門。它會被從文件中讀迷宮規劃的程序使用,並且創建相應的迷宮。或者他也許被想隨機生成迷宮的程序使用。想生成迷宮的程序把MazeFactory作爲一個參數,所以程序員可以指定房間,牆壁,門的類型。
package com.hermeslch.pattern;
public interface MazeFactory {
public Maze makeMaze();
public Wall makeWall();
public Room makeRoom(int n);
public Door makeDoor(Room r1,Room r2);
}
回憶起成員函數 CreateMaze(page 84),創建了一個由兩間房和一扇門組成的小迷宮。CreateMaze 硬編碼了這些房間和門對應的類名稱,這使得很難再用其他類型的組件來創建其他類型的迷宮。
下面代碼描述了另外一個創建迷宮的版本。
package com.hermeslch.pattern;
import org.junit.Test;
public class MazeGame {
public Maze CreateMaze(MazeFactory factory){
Maze maze = factory.makeMaze();
Room r1 = factory.makeRoom(1);
Room r2 = factory.makeRoom(2);
Door aDoor = factory.makeDoor(r1, r2);
maze.AddRoom(r1);
maze.AddRoom(r2);
r1.setSide(Direction.North,factory.makeWall());
r1.setSide(Direction.East,aDoor);
r1.setSide(Direction.South,factory.makeWall());
r1.setSide(Direction.West,factory.makeWall());
r2.setSide(Direction.North,factory.makeWall());
r2.setSide(Direction.East,factory.makeWall());
r2.setSide(Direction.South,factory.makeWall());
r2.setSide(Direction.West,aDoor);
return maze;
}
@Test
public void CreateMazeTest(){
MazeFactory factory = new EnchantedMazeFactory();
Maze maze = CreateMaze(factory);
}
}
我們可以通過繼承MazeFactory創建一個 EnchantedMazeFactory,一個可以創建被施過魔法的迷宮的工廠.EnchantedMazeFactory會重寫不同的成員函數,從而返回不同的 房間,和牆。
package com.hermeslch.pattern;
public class EnchantedMazeFactory implements MazeFactory {
@Override
public Maze makeMaze() {
// TODO Auto-generated method stub
System.out.println("EnchantedMazeFactory make maze");
return new Maze();
}
@Override
public Wall makeWall() {
// TODO Auto-generated method stub
System.out.println("EnchantedMazeFactory make Wall");
return new Wall();
}
@Override
public Room makeRoom(int n) {
// TODO Auto-generated method stub
System.out.println("EnchantedMazeFactory make EnchantedRoom");
return new EnchantedRoom(n,CastSpell());
}
@Override
public Door makeDoor(Room r1, Room r2) {
// TODO Auto-generated method stub
System.out.println("EnchantedMazeFactory make DoorNeedingSpell");
return new DoorNeedingSpell(r1,r2);
}
final protected Spell CastSpell(){
return new Spell();
}
}
現在假如我們想創建一個迷宮遊戲,這個迷宮的房間裏面有一個炸彈。如果這個炸彈被引爆,它會毀壞牆壁。我們可以創建一個Room 的子類來跟蹤這個房間是否有炸彈,以及這個炸彈是否會被引爆。我們也需要一個Wall 的子類來跟蹤被毀壞的牆壁。我們會調用子類 RoomWithABomb 和 BombedWall.
最後一個我們需要定義的類爲 BombedMazeFactory,它能夠創建被毀壞的牆壁和有炸彈的房間。
@Test
public void CreateMazeTest(){
MazeFactory factory = new BombedMazeFactory();
Maze maze = CreateMaze(factory);
}
我們發現,MazeFactroy 只是一些工廠函數的集合。這是最普通的方式來實現抽象工廠模式。下面是其他的類的一些代碼:
package com.hermeslch.pattern;
public class Bomb {
public Bomb(){
System.out.println("I am a Bomb");
}
}
package com.hermeslch.pattern;
public class BombedWall extends Wall {
public BombedWall(){
System.out.println("BombedWall created");
}
}
package com.hermeslch.pattern;
public enum Direction {
North,South,East,West
}
package com.hermeslch.pattern;
public class Door implements MapSite {
@Override
public void enter() {
// TODO Auto-generated method stub
}
public Door(Room r1,Room r2){
this.r1 = r1;
this.r2 = r2;
}
public Room otherSideFrom(Room r1){
return null;
}
private Room r1;
private Room r2;
public Room getR1() {
return r1;
}
public void setR1(Room r1) {
this.r1 = r1;
}
public Room getR2() {
return r2;
}
public void setR2(Room r2) {
this.r2 = r2;
}
public boolean isOpen() {
return isOpen;
}
public void setOpen(boolean isOpen) {
this.isOpen = isOpen;
}
private boolean isOpen;
}
package com.hermeslch.pattern;
public class DoorNeedingSpell extends Door {
public DoorNeedingSpell(Room r1,Room r2){
super(r1,r2);
System.out.println("DoorNeedingSpell created");
}
}
package com.hermeslch.pattern;
public class EnchantedRoom extends Room {
public EnchantedRoom(int roomNo,Spell sp){
super(roomNo);
this.sp = sp;
System.out.println("EnchantedRoom created");
}
private Spell sp;
@Override
public void enter() {
// TODO Auto-generated method stub
}
}
package com.hermeslch.pattern;
public interface MapSite {
public void enter();
}
package com.hermeslch.pattern;
import java.util.ArrayList;
import java.util.List;
public class Maze {
public Maze(){
}
public void AddRoom(Room e){
rooms.add(e);
}
public Room RoomNo(int index){
return rooms.get(index);
}
private List <Room> rooms = new ArrayList<Room>();
}
package com.hermeslch.pattern;
public interface MazeFactory {
public Maze makeMaze();
public Wall makeWall();
public Room makeRoom(int n);
public Door makeDoor(Room r1,Room r2);
}
package com.hermeslch.pattern;
public class Room implements MapSite {
private MapSite []sides = new MapSite[4];
private int roomNumber;
@Override
public void enter() {
// TODO Auto-generated method stub
}
public Room(int roomNo){
this.roomNumber = roomNo;
}
public MapSite getSide(Direction d){
return sides[d.ordinal()];
}
public void setSide(Direction d,MapSite ms){
sides[d.ordinal()] = ms;
}
public int getRoomNumber() {
return roomNumber;
}
public void setRoomNumber(int roomNumber) {
this.roomNumber = roomNumber;
}
}
package com.hermeslch.pattern;
public class RoomWithABomb extends Room {
Bomb bo = new Bomb();
public RoomWithABomb(int roomNo){
super(roomNo);
}
}
package com.hermeslch.pattern;
public class Spell {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.hermeslch.pattern;
public class Wall implements MapSite {
public void Wall(){
}
@Override
public void enter() {
// TODO Auto-generated method stub
}
}