原型模式
原型模式就是从一个原型克隆出多个一模一样的对象的模式。原型模式对于这样的情形适用——需要一个和已有的对象大体相同的对象,但无法接受从零开始创建该对象。举例来说,当编写 PPT 时,当上一页和当前页面元素基本一致的时候,就将上一页复制,进行一些编辑即可,从而免去再次的排版等工序。
原型模式的角色有抽象原型,具体原型和客户端。其中抽象原型类是一个提供 clone 方法的接口(Java 本身有一个这样作用的接口 Cloneable,但它仅仅起标记作用)。具体原型类是实现了该接口的类。客户端使用时调用原型类的 clone 方法。
对于 Java,clone 方法的实现,应当通过构造函数定义一个 Object 对象(在这里,Object 负责抽象原型类的角色,它就是 Prototype),其值为 super.clone(),并设定其各个域成员与被克隆的对象一致(需注意浅拷贝和深拷贝,深拷贝可以简单使用序列化接口来实现),最后强制类型转换并返回该对象。其代码形式如下——
| public Prototype clone() { Object obj = null; try { obj = super.clone(); return (Prototype) obj; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } }
|
还可使用所谓的原型管理器——其实质是一个、<String, Prototype>的 HashMap,其通过单例模式创建,维护着所需的原型对象,并应客户端的要求返回特定的对象。
原型模式的优点在于能够节约初始化的成本(特别是需要调用硬盘甚至网络资源的时候),简化复杂实例的创建,且其扩展性高,可以针对抽象原型类进行编程,而具体原型类编写在配置文件中,增加,减少具体原型类都不需要修改源代码(甚至修改也可能可以不需要,不过可能会造成配置文件繁琐?)。
缺点在于深拷贝时代码可能比较复杂,且克隆方法必须位于类的内部,修改时需要修改源代码,违反开闭原则。
Spring 的 IoC 容器对 Bean 的创建可以选择使用单例模式或原型模式。
建造者模式
建造者模式就像游戏中的捏人系统,人的外观分为几个特定的部分,如发型,脸型,五官,躯干……一次捏人的完成,是对各个部分都进行“建造”,最后将其组合出结果(当然,这里没有组合,或许也可以联想飞船的各模块的建造……就像 Space Engineer 里那样的?)。
建造者模式的实质就是将复杂对象(具有多个实例域的对象)的构建和对象的表示(其属性)相分离,建造者将对象所属的类中各个共通的部分抽象成一个个模块,按模块地进行建设(比如划分为登陆模块,控制模块,推进模块,战斗模块……),最后将各个模块进行组合,得出最终的舰船。而非是直接上手创建这样一个复杂的对象——抽象啊抽象!这就和用汇编写 http 服务器一样离谱。
建造者模式有如下角色:抽象建造者——提供各部分的构造方法的接口,以及返回最终对象的方法的接口。这里的抽象建造者可以是抽象类(它可以维护一个最终产品并直接实现返回最终对象的方法),也可以是接口;产品——建造的对象;建造者——对抽象建造者的实现;导演——指挥建造者进行建造并返回结果(也就是调用建造者提供的方法返回最终对象)(这个导演倒是可以直接设定成抽象建造者的一个方法…这便使用了类似模板方法的一种设计…这样简化了系统的结构,但可能会让抽象建造者类职责过重);客户端——与导演交互,获取最终对象。
类图如下——
然后是各个角色的一般形式——
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Product { private dataA partA; private dataB partB; private dataC partC; }
abstract class Builder { protected Product product = new Product(); public abstract void buildPartA(); public abstract void buildPartB(); public abstract void buildPartC(); public Product getResult() { return product; } }
class Director { private Builder builder;
public Director(Builder builder) { this.builder = builder; }
public Product construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); return builder.getResult(); } }
class ConcreteBuilder extends Builder { public void buildPartA() { }
public void buildPartB() { }
public void buildPartC() { } }
|
客户端形式如下——
| public class Main { public static void main(String[] args) { Builder builder = new ConcreteBuilder(); Director director = new Director(builder); Product product = director.construct(); } }
|
可见还是简化后的形式更加舒服。
关于建造过程,其实能够给其添加更多程序结构来更加精细地控制建造过程。比如添加所谓的钩子方法(Hook Method),它控制是否需要调用某个 buildPartX() 方法。
钩子方法的形式是置于抽象建造者类中的一个返回布尔值的方法,来决定某个部分的是否构建。比如对于一个建造船舰的抽象建造者类 ShipBuilder,给定一个名为 isArmed 的方法,对于运输船,它不需要火力模块,因此对于这个方法它就返回 false。导演类就可以这样 construct——
| public Product construct() { shipBuilder.buildPowerModule(); if (shipBuilder.isArmed()) shipBuilder.buildFireModule(); return builder.getResult(); }
|
一个练习——某视频播放软件有多种界面显示模式——标准模式,精简模式,编辑模式等等。不同界面显示模式中组成元素有差异。其中,各界面现实如下元素——
- 标准模式:播放窗口,控制条,显示菜单,播放列表
- 编辑模式:播放窗口,控制条,显示菜单,编辑栏
- 精简模式:播放窗口,控制条
可以使用一些钩子函数对窗口的构建进行精细控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
class Window { }
abstract class UIBuilder { private Window window = new Window(); public abstract void buildPlayWindow(); public abstract void buildCtrlBar(); public abstract void buildDisplayMenu(); public abstract void buildPlayList(); public abstract void buildEditBar(); public boolean needDisplayMenu() { return true; } public boolean needPlayList() { return true; } public boolean needEditBar() { return false; } public Window createWindow() { return window; } public static final Window construct(UIBuilder builder) { builder.buildPlayWindow(); builder.buildCtrlBar(); if (builder.needDisplayMenu()) builder.buildDisplayMenu(); if (builder.needPlayList()) builder.buildPlayList(); if (builder.needEditBar()) builder.buildEditBar(); return builder.createWindow(); } }
class StandardWindowBuilder extends UIBuilder {
@Override public void buildPlayWindow() { System.out.println("标准模式:播放窗口"); }
@Override public void buildCtrlBar() { System.out.println("标准模式:控制条"); }
@Override public void buildDisplayMenu() { System.out.println("标准模式:显示菜单"); }
@Override public void buildPlayList() { System.out.println("标准模式:播放列表"); } @Override public void buildEditBar() { System.out.println("不应该被执行"); } @Override public boolean needDisplayMenu() { return true; } @Override public boolean needEditBar() { return false; } @Override public boolean needPlayList() { return true; } } class EditWindowBuilder extends UIBuilder {
@Override public void buildPlayWindow() { System.out.println("编辑模式:播放窗口"); } @Override public void buildCtrlBar() { System.out.println("编辑模式:控制条"); } @Override public void buildDisplayMenu() { System.out.println("编辑模式:显示菜单"); }
@Override public void buildPlayList() { System.out.println("不应该被执行"); } @Override public void buildEditBar() { System.out.println("编辑模式:编辑条"); } @Override public boolean needDisplayMenu() { return true; } @Override public boolean needEditBar() { return true; } @Override public boolean needPlayList() { return false; } } class MinusWindowBuilder extends UIBuilder {
public void buildPlayWindow() { System.out.println("精简模式:播放窗口");
} public void buildCtrlBar() { System.out.println("精简模式:控制条"); } public void buildDisplayMenu() { System.out.println("不应该被执行"); }
public void buildPlayList() { System.out.println("不应该被执行"); }
public void buildEditBar() { System.out.println("不应该被执行"); } @Override public boolean needDisplayMenu() { return false; } @Override public boolean needEditBar() { return false; } @Override public boolean needPlayList() { return false; } }
public class Main { public static void main(String[] args) { Window window = UIBuilder.construct(new EditWindowBuilder()); } }
|
天啊……这代码长的离谱。
建造者模式的优点在于,客户端不需要知道产品内部的细节(这同工厂模式是一样的),产品本身同产品的构建分离(客户端不需要自己 new 和其他初始化操作);每一个具体建造者是相对独立的,系统扩展方便,符合开闭原则;产品的创建过程能够分散在不同的方法中,从而更加清晰,易维护。
缺点在于,其只能创建有较多共同点的产品(如人的外观,船舰等),如果差异性很大(比如人类和外星人的船舰,人和动物),很多部分都不相同(从而不能抽象出一般的创建过程),就无法使用建造者模式。
另一种实现
下面在公众号上看到的对建造者模式的另一种实现。客户端使用new Ship.Builder("powerModule").setFireModule("fireModule").setTransportModule("transportModule").build();
这样的形式进行调用。这也可以通过另一个类(那么其实它才是真正的“建造者”)来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| class Ship { private String powerModule; private String fireModule; private String transportModule; private Ship(Builder builder) { this.powerModule = builder.powerModule; this.fireModule = builder.fireModule; this.transportModule = builder.transportModule; } public static class Builder { private String powerModule; private String fireModule; private String transportModule; public Builder(String powerModule) { this.powerModule = powerModule; } public Builder setFireModule(String fireModule) { this.fireModule = fireModule; return this; } public Builder setTransportModule(String transportModule) { this.transportModule = transportModule; return this; } public Ship build() { return new Ship(this); } } }
public class Main { public static void main(String[] args) { Ship ship = new Ship.Builder("powerModule") .setFireModule("fireModule") .setTransportModule("transportModule") .build(); } }
|