Java的23种设计模式详解整理之创建型模式


最近重新阅读"四巨头"的设计模式. 对一些设计模式有了更多的理解. 原著中的例子是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).

有两种迷宫(Maze): 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

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

定义Factory接口

public interface 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);
}

定义实现,

public class EnchantedMazeFactoryimplements 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
       Maze maze =factory.makeMaze();
     // make room
       Room room1 =factory.makeRoom(1);
       Room room2 =factory.makeRoom(2);
    // make door
       Door door =factory.makeDoor(room1,room2);
      // add room
       maze.addRoom(room1);
       maze.addRoom(room2);
       return maze;
    }
}

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

定义client

    @Test
    public void test() {
       MazeGame game =new MazeGame();
       MazeFactory factory =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定义

public class Room {
    private int n;
}
public class Maze {
    private int roomNo;
    public Room roomNo(intn);
    public void add(Roomroom);
}

定义Director

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

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

定义Builder

public interface MazeBuilder {
    public void buildMaze();
    public void buildRoom(introom);
    public void buildDoor(introomFrom,introomTo);
    public Maze getMaze();
    public int getCount();
}

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

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

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

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

UT

@Test

 public void test() {
        MazeGame game =new MazeGame();
        MazeBuilder builder =new StandardMazeBuilder();
        // game.cteateMaze(builder);
        game.createComplexMaze(builder);
        Maze maze =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, 如何实现

方法一(浅拷贝):

java为克隆提供了Cloneable克隆接口,实现这个接口就ok

方法二(深拷贝):

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

参考代码:

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

     ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
      // set the bytes of the object to an byte array.
      ObjectOutputStream out =new ObjectOutputStream(arrayOutputStream);
    // the source had implemented theSerializable interface
      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

时间: 10-13

Java的23种设计模式详解整理之创建型模式的相关文章

Java开发中的23种设计模式详解(转)

设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

0. Java开发中的23种设计模式详解(转)

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

Java开发中的23种设计模式详解

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

Java开发中的23种设计模式详解(转载)

设计模式(Design Patterns) --可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

开发中的23种设计模式详解

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

Java的23种设计模式(转)

设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因

Java经典23种设计模式之结构型模式(二)

接上篇,本文介绍结构型模式里的组合模式.装饰模式.外观模式. 一.组合模式(Composite) 组合模式:将对象组合成树形结构,表示"部分--整体"的层次结构.最终达到单个对象和组合对象的使用具有一致性.单看这句话貌似有点抽象,其实比较简单. 以李云龙的独立团为例,目的要统计赵嘉宇一战共歼灭敌人多少个.最高的级别是团,一个团有若干个营,一个营有若干个排,一个排有若干个战士.(为了简化问题,排下面就不设行政单位了).很自然的,李云龙给营长开会回去给老子统计.营长回去给各个排长开会,赶紧

java 的23种设计模式 之单身狗和隔壁老王的故事

觉得代码写的别扭了,回头翻翻java 的23种设计模式.today,额,这么晚了,困了.就弄个最简单的单例模式吧. 单例模式:俗称单身狗 package singleton; public class SingleTon { private static final class SingleTonBuilder { private static SingleTon singleTon = new SingleTon(); } private SingleTon() { } public stat

Java经典23种设计模式之行为型模式(三)

本文接着介绍11种行为型模式里的备忘录模式.观察者模式.状态模式. 一.备忘录模式 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可以将该对象恢复到原先保存的状态.还是比较好理解的. 1.Memento 备忘录存储原发器对象的内部状态,这个类就是要存储的对象的状态.状态需要多少个变量,在Memento里就写多少个变量. public class Memento { private String state; public Meme*to(String st