设计模式之抽象工厂(abstract factory)

设计模式之抽象工厂(abstract factory)

  • 作者:Geticsen
  • 时间:2019-10-10
  • 45人已阅读
简介 考虑一个ui的工具箱,使它能够支持多种风格标准,例如 Motif 和 Presentation Manager.不同的风格标准为ui组件如滚动条,窗口,按钮 定义了不同的展示和行为。为了使程序可以在不同的风格标准中自由切换,应用程序不应该为了实现某种外观而进行硬编码。在应用程序中实例化特定的风格标准组件类使得我们后面很难改变风格。

设计模式之抽象工厂(abstract factory)

目的

        为创建相关对象或者互相依赖的对象提供一个接口,并且不指出他们的实现类。

动机

       考虑一个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 {
    private Room r1;
    private Room r2;
    private boolean isOpen;

    public Door(Room r1, Room r2) {
        this.r1 = r1;
        this.r2 = r2;
    }

    @Override
    public void enter() { // TODO Auto-generated method stub	
    }

    public Room otherSideFrom(Room r1) {
        return null;
    }

    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;
    }
}
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 {
    private List<Room> rooms = new ArrayList<Room>();

    public Maze() {
    }

    public void AddRoom(Room e) {
        rooms.add(e);
    }

    public Room RoomNo(int index) {
        return rooms.get(index);
    }
}
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;

    public Room(int roomNo) {
        this.roomNumber = roomNo;
    }

    @Override
    public void enter() { // TODO Auto-generated method stub
    }

    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
	}
}


文章评论

Top