创建者模式之生成器
设计模式 

构建复杂对象的生成器 – Builder

生成器模式适合于创建一个对象很复杂的时候。

意图

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

适用性

以下情况适用Builder模式

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时
  • 当构造过程必须允许被构造的对象有不同的表示时

结构

Directorconstruct()for all objects in structure {buider.buildPart();}BuilderbuildPart()ConcreteBuilderbuildPart()getResult()Product

参与者

上图中的各个类的角色:

  • Builder: 为创建一个 Product 对象的各个部件指定抽象接口
  • ConcreteBuilder: 实现 Builder 的接口以构造和装配该产品的各个部件;定义并明确它所创建的表示;提供一个检索产品的接口
  • Director: 构造一个使用Builder接口的对象
  • Product: 表示被构造的复杂对象,ConcreteBuilder 创建该产品的内部表示并定义它的装配过程;包含了定义组成部件的类,包括将这些部件组装成最终产品的接口。

目的

  1. 它使你可以改变一个产品的内部表示。Builder对象提供给导向器一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构,所以改变该产品的内部表示所要做的只是定义一个新的生成器
  2. 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息;这些类是不出现在Builder接口中的。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码
  3. 它使你对构造过程进行更精细的控制。Builder模式同一下就生成产品的创建型模式不同,它是在 Director 的控制下一步步构造产品的。因此Builder接口相比其他创建型模式更能反映产品的构造过程,也可以让我们更精细地控制构造过程。

实现

  1. 装配和构造接口。 生成器逐步构造它们的产品,因此Builder类接口必须足够普遍,以便为各种类型的具体生成器构造产品。
  2. 产品没有抽象类。 具体生成器生产的产品,它们表示相差如此之大以至于给不同的产品以公共父类没有太大的意义。
  3. Builder中缺省的方法为空。 这样客户只需要重定义所感兴趣的操作

我们定义一个 Maze 的 Builder(缺省方法为空):

public class MazeBuilder {
    public void buildMaze(){}
    public void buildRoom(int room){}
    public void buildDoor(int roomFrom, int roomTo){}
    public Maze getMaze(){return null;}
}

然后 createMaze 就变成了:

public Maze createMaze(MazeBuilder builder) {
    builder.buildMaze();
    builder.buildRoom(1);
    builder.buildRoom(2);
    builder.buildDoor(1,2);

    return builder.getMaze();
}

同其他创建型模式相同,Builder模式封装了对象是如何被创建的,我们可以重用 MazeBuilder 来创建不同种类的迷宫。 一个简单的迷宫实现如下:

public class StandardMazeBuilder extends MazeBuilder{

    public StandardMazeBuilder(){}
    @Override
    public void buildMaze(){
        currentMaze = new Maze();
    }
    @Override
    public void buildRoom(int n){
        if(currentMaze.getRoom(n) == null) {
            Room room = new Room(n);
            currentMaze.addRoom(room);

            room.setSide(Direction.North, new Wall());
            room.setSide(Direction.South, new Wall());
            room.setSide(Direction.East, new Wall());
            room.setSide(Direction.West, new Wall());
        }
    }
    @Override
    public void buildDoor(int roomFrom, int roomTo){
        Room room1 = currentMaze.getRoom(roomFrom);
        Room room2 = currentMaze.getRoom(roomTo);
        Door d = new Door(room1, room2);

        room1.setSide(commonWall(room1, room2), d);
        room2.setSide(commonWall(room2, room1), d);
    }
    @Override
    public Maze getMaze(){return currentMaze;}
    private Direction commonWall(Room r1, Room r2){
        return Direction.South;
    }


    private Direction direction;
    private Maze currentMaze;
}

我们可以用如下方式创建一个简单的迷宫:

Maze maze;
MazeGame game;
StandardMazeBuilder builder;
game.createMaze(builder);
maze = builder.getMaze();

相关模式

AbstractFactory 同 Builder 相似,因为它也可以创建复杂对象。主要区别是 Builder 模式着重于一步步构造一个复杂对象;而AbstractFactory着重于多个系列的产品对象(无论简单还是复杂) Composite 通常是 Builder 生成的

创建固定产品的工厂方法 – Factory Method

意图

定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method 使一个类的实例化延迟到其子类。

适用性

下列情况可以使用 Factory Method:

  • 当一个类不知道它所必须创建的对象的类的时候
  • 当一个类希望由它的子类来指定它所创建的对象的时候
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望将哪一个帮助子类时代理者这一信息局部化的时候

结构

product = FactoryMethod()CreatorFactoryMethod()AnOperation()ConcreteCreatorFactoryMethod()ProductConcreteProductreturn new ConcreteProduct

参与者

  • Product: 定义工厂方法所创建的对象的接口
  • ConcreteProduct: 实现 Product 接口
  • Creator: 声明工厂方法,该方法返回一个 Product 类型的对象。Creator 也可以定义一个工厂方法的缺省实现,返回一个缺省的ConcreteProduct 对象;可以调用工厂方法创建一个 Product 对象
  • ConcreteCreator: 重定义工厂方法以返回一个 ConcreteProduct 实例

效果

工厂方法不再将与特定应用有关的类绑定到代码中,代码仅仅处理 Product 接口,因此它可以与用户定义的任何 ConcreteProduct 一起使用。 其潜在缺点在于客户可能仅仅为创建一个特定的 ConcreteProduct 对象,就不得不创建 Creator 子类。 当 Creator 子类不是必须的时候,客户必须自己处理类原话的其他方面。

  1. 为子类提供挂钩。 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活
  2. 连接平行的类层次。 工厂方法并不往往只是被 Creator 调用,客户可以找到一些有用的工厂方法,尤其在平行类层次的情况下。(当一个类将它的一些职责委托给一个独立的类的时候,就产生了平行类层次。)

实现

  1. 有两种不同的情况。 Creator 类是一个抽象类并且不提供它所声明的工厂方法的实现,避免了不得不实例化不可预见类的问题;Creator是一个具体类,提供一个缺省的实现,更具有灵活性。
  2. 参数化工厂方法。 工厂方法可以采用一个标识要被创建的对象种类的参数。通过解析该参数,可以确定要实力化的是哪个产品。

代码示例

创建一个 MazeGame 的缺省实现类,定义了缺省的方法

public class MazeGame {
    public Maze makeMaze() {
        return new Maze();
    }

    public Room makeRoom (int n) {
        return new Room(n);
    }

    public Wall makeWall () {
        return new Wall();
    }

    public Door makeDoor (Room r1, Room r2) {
        return new Door(r1, r2);
    }

    /**
     * 用工厂方法重新 createMaze
     */
    public Maze createMaze () {
        Maze maze = makeMaze();
        Room r1 = makeRoom(1);
        Room r2 = makeRoom(2);
        Door theDoor = makeDoor(r1, r2);
        maze.addRoom(r1);
        maze.addRoom(r2);
        r1.setSide(Direction.North, makeWall());
        r1.setSide(Direction.East, theDoor);
        r1.setSide(Direction.South, makeWall());
        r1.setSide(Direction.West, makeWall());

        return maze;
    }
}

继承 MazeGame,子类覆盖某些方法,以创建自己的内容需要。

public class EnchantedMazeGame extends MazeGame {
    @Override
    public Room makeRoom (int n) {
        return new EnchantedRoom(n);
    }

    @Override
    public Door makeDoor (Room r1, Room r2) {
        return new DoorNeedSpell(r1, r2);
    }
}

相关模式

工厂方法通常在 Template Method 中被调用 抽象工厂经常用工厂方法来实现。

原型模式 – Prototype

意图

用原型实例指定创建对象的种类,并通过 拷贝 这些原型对象创建新的对象

适用性

当一个系统应该独立于它的产品创建、构成和表示时,要用到 Prototype 模式;以及:

  • 当要实例化的类时在运行时刻指定时,例如:动态装载
  • 为了避免创建一个与产品类层次平行的工厂类层次时
  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实力话类更方便

结构

Prototypeclone()ConcretePrototype1clone()return copy of selfConcretePrototype2clone()return copy of selfp=prototype.clone()Clientprototypeoperation()

参与者

  • Prototype: 声明一个克隆自身的接口
  • ConcretePrototype: 实现一个克隆自身的操作
  • Client: 让一个原型克隆自身从而创建新的对象

效果

Prototype模式的一些优点:

  1. 运行时刻增加和删除产品。 Prototype允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。
  2. 改变值以指定新对象。 高度动态的系统允许你通过对象符合定义新的行为而不是定义新的类,例如:通过一个对象变量指定值。通过实例化已有类并且将这些实例注册为客户对象原型,就可以有效定义新类别的对象。
  3. 改变结构以指定新对象。 许多应用由部件和子部件来创建对象。例如电路设计编辑器就是由子电路来构造电路的。这样的应用通常允许实例化复杂的、用户定义的结构,可以重复使用一个特定的子电路。Prototype 模式也支持这一特性。我们只需要将这个子电路作为一个原型增加到可用的电路选择板中。只要符合电路对象将Clone实现为一个深拷贝,具有不同结构的电路就可以是原型了。
  4. 减少子类的构造。 工厂方法经常产生一个与产品类层次平行的Creator类层次。Prototype模式使得我们克隆一个原型而不是请求工厂方法生产一个新对象,因此不需要Creator层次。
  5. 用类动态配置应用。 一些运行时刻环境运行你动态将类装载到应用中。
  6. 主要缺陷是需要每一个Prototype子类都实现Clone操作。 在有些情况下,比较困难。例如,当所考虑的类不支持拷贝或有循环引用时,就比较困难。

实现

当实现原型时,需要考虑如下问题:

  1. 使用一个原型管理器。 当一个系统中原型数目不固定时,要保持一个可用原型的注册表。客户不会自己来管理原型,但会在注册表中存储和检索原型。
  2. 实现克隆操作 克隆一个结构复杂的原型通常需要深拷贝,因为复制对象和原对象必须相互独立。
  3. 初始化克隆对象 原型的类应该为一些关键的状态值定义好了操作,客户可以在克隆对象之后马上就可以使用这些操作。

代码示例

因为需要深拷贝,下面列出 Maze 类的深拷贝,其他类似:

public class Maze implements Cloneable {
    private List<Room> rooms = new ArrayList<Room>();
    public void addRoom (Room room) {
        rooms.add(room);
    }
    public Room getRoom (int i) {
        return rooms.get(i);
    }
    @Override
    public Maze clone() {
        Maze maze = null;
        try {
            maze = (Maze) super.clone();
            // 需要注意浅拷贝和深拷贝
            maze.rooms = new ArrayList<>(this.rooms.size());
            Collections.copy(maze.rooms, this.rooms);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return maze;
    }
}

MazePrototypeFactory 用一个原型为参数的构造器来扩充 MazeFactory 接口:

public class MazePrototypeFactory implements MazeFactory {

    private Maze prototypeMaze;
    private Room prototypeRoom;
    private Wall prototypeWall;
    private Door prototypeDoor;

    public MazePrototypeFactory(Maze prototypeMaze, Room prototypeRoom, Wall prototypeWall, Door prototypeDoor) {
        this.prototypeMaze = prototypeMaze;
        this.prototypeRoom = prototypeRoom;
        this.prototypeWall = prototypeWall;
        this.prototypeDoor = prototypeDoor;
    }

    @Override
    public Maze makeMaze() {
        return prototypeMaze.clone();
    }

    @Override
    public Wall makeWall() {
        return prototypeWall.clone();
    }

    @Override
    public Room makeRoom(int roomNo) {
        return prototypeRoom.clone();
    }

    @Override
    public Door makeDoor(Room r1, Room r2) {
        Door door = prototypeDoor.clone();
        door.initialize(r1, r2);
        return door;
    }
}

我们只需要使用基本迷宫构建的原型进行初始化,就可以创建一个原型或者缺省的迷宫:

MazeGame game = new MazeGame();
MazePrototypeFactory simpleMazeFactory = new MazePrototypeFactory(new Maze(), new Room(), new Wall(), new Door());
Maze maze = game.createMaze(simpleMazeFactory);

为了改变迷宫的类型,我们可以用一个不同的原型集合来初始化 MazePrototypeFactory:

MazePrototypeFactory enchantedFactory = new MazePrototypeFactory(new EnchantedMaze(), new EnchantedRoom(),
            new Wall(), new DoorNeedSpell());
Maze maze1 = game.createMaze(enchantedFactory);

相关模式

Prototype 和 AbstractFactory模式在某种方面是相互竞争的,但他们也可以一起使用。AbstractFactory 可以存储一个被克隆的原型集合,并且返回产品对象。 大量使用Composite 和 Decorator 模式的设计通常也可以使用 Prototype

创建只有一个对象的单件模式 – Singleton

创建只有一个对象实例的类。

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性

下面情况使用Singleton模式:

  • 当类智能有一个实例而且客户可以从一个全局访问点访问它
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例。

结构

Singletonstatic uniqueInstancesingletonDatastatic getInstance()singletonOperation()getSingletonData()return uniqueInstance

参与者

  • Singleton: 定义一个 getInstance 来允许客户访问它唯一实例;负责创建唯一实例

效果

Singleton模式有许多有点:

  1. 对唯一实例的受控访问
  2. 缩小命名空间。 Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染命名空间
  3. 允许对操作和表示的精化。 Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用很容易。可以用你所需要的类的实例在运行时刻配置应用
  4. 比类操作更灵活

创建型模式总结

用一个系统创建的那些对象的类对系统进行参数化有两种常用的方法:

  1. 生成创建对象的类的子类;这对应于使用 Factory Method 模式。这种方法的主要缺点是,仅为了改变产品类,就可能需要创建一个新的子类。
  2. 对系统进行参数化的方法更多的依赖于对象复合:定义一个对象负责声明产品对象类,并将它作为系统参数。这是 Abstract Factory、Builder 和 Prototype 模式的关键特征。 Abstract Factory由这个工厂对象产生多个类的对象。Builder由这个工厂对象使用一个相对复杂的协议,逐步创建一各复杂的对象。Prototype由该工厂对象通过拷贝原型对象来创建产品对象。

使用 Abstract Factory、Prototype或Builder的设计比使用Factory Method的那些设计更灵活,但也更复杂。设计以使用 Factory Method 开始,并且当设计者发现需要更大的灵活性时,设计会向其他创建型模式演化。

local_offer #设计模式 
navigate_before navigate_next