最近重新阅读"四巨头"的设计模式. 对一些设计模式有了更多的理解. 原著中的例子是C++写的,不好理解. 这里我换成了Java, 代码示例仅供参考,没有具体实现.

介于个人水平有限,如有纰漏,请指正.有问题的朋友可以私信我或者发我邮箱(请到我主页查看),我看到就会回复. 希望和大家一起进步.

工作中有时候最困难的不是怎么去实现一个功能,而是怎么去设计一个功能.我常常会因为频繁改动需求大费脑筋.之后我在思考如何将一个功能在设计之初就做好扩展的准备,防止需求变动导致大面积的修改.code之前想好设计会事半功倍.

废话不说,开始!

Java面向对象少不了复杂对象的创建, 如何创建复杂类型对象,就是我们要研究的课题, 那么就少不了创建类型的模式

一,创建型模式: Abstract Factory 模式, Builder模式, Factory Method模式 以及Prototype模式

Factory Method 模式和Prototype模式这里不做过多解释. 重点关注前两个.

abstract factory 抽象工厂

1, 意图

        提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类

   注: 1) 这里注意一下, Abstract Factory创建的是一系列相关或相互依赖的对象

         2) 何为相关或依赖? 相关或依赖可以理解为构建composite object(复杂对象)所需要的components(组件)

2, 案例

       迷宫(Maze):包含有房间(Room), 门(Door), 墙(Wall).

Enchanted(魔法的) 和 Bomed(可爆炸的),

       如何生成这些Maze?

3,解决方式

       定义一个抽象的MazeFactory, 这个接口声明了用来创建每一类Maze组件的接口. 每一Maze组件都有一个抽象类,而具体子类则实现特定Maze.而对于每一个抽象Maze组件类, MazeFactory接口都有一个返回新Maze组件的对象的操作.客户端调用组件实例,但是客户端并不知道具体实例类型.

4,适用场景

      1) 一个系统要独立于它的产品的创建,组合和表示

      2) 一个产品需要多个产品系列装配

5, 参与者分析

AbstractFactory(MazeFactory)

    --声明一个创建抽象产品对象的操作接口

ConcreteFactory(EnchantedMazeFactory,BomedMazeFactory)

    --实现创建具体产品对象的操作

AbstractProduct(Room, Door, Wall)

    --为一类产品对象声明一个接口

ConcreteProduct(EnchantedMaze,BomedMaze)

   --定义一个将被相应的具体工厂创建的产品对象

   --实现AbstractProduct接口

Client

   --仅仅使用AbstractFactory和AbstractProduct类声明的接口

6,Abstract Factory模式分析

1) 分离了具体的类

2) 易于产品交换 

BomedMazeFactory可以直接替换EnchantedMazeFactory.

3) 有利于产品的一致性 

    一个应用一次只能使用同一个系列中的对象

4)  难以支持新产品 

    如果添加新的产品,就需要扩展AbstractFactory及其子类

7, 实现

1)      Factory应该是单例的

2)      AbstractFactory只负责抽象,具体实现在ConcreteFactory中. 可以为每一个产品定义一个工厂方法.而具体的工厂将为每个产品override该Factory指定的方法(前提是每个产品系列都需要有一个新的工厂类,也就是产品差别小)

8, 代码实现

定义model

publicclass Door {}
public class Maze {
    publicvoid addRoom(Roomroom1);
}
public class Room {}
public class Wall {}

 

定义Factory接口

publicinterface MazeFactory {
   // make a maze
    public Maze makeMaze();
   // make wall
    public Wall makeWall();
   // make room
    public Room makeRoom(intn);
   // make door
    public Door makeDoor(Roomroom1, Room room2);
}

 

定义实现, 

publicclassEnchantedMazeFactoryimplements MazeFactory {
    @Override
    public Maze makeMaze();
    @Override
    public Wall makeWall();
    @Override
    public Room makeRoom(intn);
    @Override
    public Door makeDoor(Roomroom1, Room room2);
}
 
public class BomedMazeFactory implements MazeFactory {
    @Override
    public Maze makeMaze();
    @Override
    public Wall makeWall();
    @Override
    public Room makeRoom(intn);
    @Override
    public Door makeDoor(Roomroom1, Room room2);
}
 
publicclass MazeGame {
    public Maze createMaze(MazeFactoryfactory){
      // make maze
       Mazemaze =factory.makeMaze();
     // make room
       Roomroom1 =factory.makeRoom(1);
       Roomroom2 =factory.makeRoom(2);
    // make door
       Doordoor =factory.makeDoor(room1,room2);
      // add room
       maze.addRoom(room1);
       maze.addRoom(room2);
       return maze;
    }
}

注意:这里的Factory实际生产的不是一个完整的Maze, 而是一个个单独的组件, 然后组件通过MazeGame.createMaze方法才组装起来

 

定义client

 

@Test
    publicvoid test() {
       MazeGamegame =new MazeGame();
       MazeFactoryfactory =new EnchantedMazeFactory();
       //factory = newBomedMazeFactory();
       Mazemaze =game.createMaze(factory);
    }

注意:这里不需要额外的get方法, 而是直接返回maze对象


 builder(生成器)

1, 意图

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

2, 案例

    一个RTF(Rich Text Format)文档交换格式的阅读器能将RTF转换为多种正文格式, 该阅读器可以将RTF文档转化为普通的文本或者其他.

3, 可能遇到的问题

   可能转化的数目是无限的,因此要能够很容易实现新的转换的增加,同时不改变RTF阅读器.

4,解决

    可以将RTF转换成另一种正文表示的TextConvert对象配置这个RTFReader类. 当RTFReader对RTF文档进行分析,它使用TextConvert去转换,无论何时Reader识别了一个RTF标记,它都发送一个请求给TextConvert去转换这个标记. TextConvert对象负责进行数据转换以及用特定格式表示该标记.

    每一个转化器类在该模式中称为生成器builder,而阅读器则称为导向器director. Builder模式

将分析文本格式的算法与描述怎样创建和表示一个转换后格式的算法分离出来.这样可以重用RTFReader的语法分析算法, 根据文档创建不同的正文表示则仅仅需要不同的TextConvert就ok.

5,适用场景

   1)创建复杂对象的算法应该独立于该对象的组成部分以及他们的装配方式时

   2)当构造过程必须允许被构造的对象有不同的表示

6,分析这个过程的参与者

Builder(TextConvert)

    --为创建一个Product对象的各个部件指定抽象接口

ConcreteBuilder(ASCIIConvert, TeXConvert,TextWidgetConvert)

    --实现Builder的接口,用来构造和装配Product的各个部件

Director(RTFReader)

    --构造一个使用Builder的实例

Product(ASCIIText. TeXText, TextWidget)

    --表示被构造的复杂对象. ConcreteBuilder创建该产品内部表示并定义它的装配过程

    --包含定义组成部件的类,包括将这些部件装配成最终产品的接口

7,builder模式的优点

    1)可以改变一个产品的内部表示:

        Builder对象提供给Director一个构造产品的抽象接口. 该接口使得生成器隐藏这个产品的表示和内部结构.同时隐藏了产品是如何装配的. 那么对于不同的Produc表示方式,只需要实现这个接口就OK

    2)将构造代码和表示代码分开: 

      Builder封装了创建和表示,外界不需要了解定义产品内部结构的类的所有信息;这些类是不出现在Builder接口中的.每个ConcreteBUilder包含了创建和装配一个特定产品的所有代码.

   3)精细控制构建过程: 

      Builder和直接生成产品的创建模型不同, builder在Direct的控制下一步一步构造产品的.当product完成时, Director才会get到result.因此builder更能反映product的构造过程,可以更精细控制product内部结构

8,实现分析

Builder会为每个构件(Director要求的构件)定义对应的操作.具体的操作实现会在ConcreteBuilder中实现

1)装配和构造接口

生成器逐步构造产品,因此builder类需要高度抽象.

2)product为什么没有抽象类,

考虑到builder生产的product结构差距较大,难以抽象出父类.ASCIIText和TextWidget对象抽象不出公用接口,并且抽象出这样的接口没有任何意义. 因为client给Director适配具体的Builder

3)builder中缺省的方法是空,用于override

 

案例及分析

Model定义

publicclass Room {
    privateintn;
}
public class Maze {
    privateint roomNo;
    public Room roomNo(intn);
    publicvoid add(Roomroom);
}

定义Director

public class MazeGame {
    Maze cteateMaze(MazeBuilderbuilder) {
        builder.buildMaze();
        builder.buildRoom();
        builder.buildDoor();
        returnbuilder.getMaze();
    }
    Maze createComplexMaze(MazeBuilderbuilder) {
        builder.buildRoom();
        builder.buildRoom();
        returnbuilder.getMaze();
    }
}

 注意:这里的Builder是没有返回值的,也就是说,对象被封装在了builder中,装配工作将在builder中进行, MazeGame不需要知道装配细节


定义Builder

publicinterface MazeBuilder {
    publicvoid buildMaze();
    publicvoid buildRoom(introom);
    publicvoid buildDoor(introomFrom,introomTo);
    public Maze getMaze();

publicint getCount();
}

定义Builder的子类,创建标准的Maze

public class StandardMazeBuilder implements MazeBuilder {
    private MazecurrentMaze;
    private DirectioncommonWall;
    public StandardMazeBuilder() {
        this.currentMaze = new Maze();
    }
    publicvoid buildMaze();
    public void buildRoom(int room);
    public void buildDoor(int roomFrom, int roomTo);
    public Maze getMaze();
}

定义Builder的子类,不生成迷宫, 只记录创建maze数量的builder

publicclass CountingMazeBuilderimplements MazeBuilder {
    publicvoid buildMaze();
    public void buildRoom(introom) ;
    public void buildDoor(introomFrom,int roomTo) ;
    public Maze getMaze();
    publicint getCount();
}


UT

@Test
    public void test() {
        MazeGamegame =new MazeGame();
        MazeBuilderbuilder =new StandardMazeBuilder();
        // game.cteateMaze(builder);
        game.createComplexMaze(builder);
        Mazemaze =builder.getMaze();
    }

 注意:这里和abstract factory不同,使用了单独的get方法,而不是像abstract factory一样直接返回对象


总结:

1,builder模式的目的是将模型的创建和表示分离开,所以当模型有多个表示的时候可以考虑使用这种模式

2,builder和abstract factory比较

    builder和abstract factory很相似,都可以创建复杂的对象.

    build是精细控制一步一步创建对象.abstractfactory侧重于多个类型的复杂对象创建.

    builder在最后通过get得到复杂对象.而abstractfactory则是立即return

   复杂的对象建议使用Builder,可以在ConcreteBuilder中定义具体细节.

 附加:关于Prototype模式

 1, 意图

    从源对象克隆一个目标对象( A克隆成A')

2, 如何实现

  方法一(浅拷贝): 

克隆接口,实现这个接口就ok

  方法二(深拷贝):

序列化接口, 并使用对象流clone新对象,这种clone适合深拷贝

 参考代码:

    source是源对象,target 是克隆的目标对象


  

ByteArrayOutputStream arrayOutputStream=newByteArrayOutputStream();
      // set the bytes of the object to an byte array.
      ObjectOutputStream out =new ObjectOutputStream(arrayOutputStream);
    // the source had implemented theSerializable
      out.writeObject(source);
      // get the array metioned before
      ByteArrayInputStream arrayInputStream=new ByteArrayInputStream(arrayOutputStream.toByteArray());
      ObjectInputStream  input= new ObjectInputStream(arrayInputStream);
      Target target = input.readObject();

 注意:这里使用的是ByteArrayOutputStrea和ByteArrayInputStream