以Java
语言为例。
分类
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
创建型模式
工厂方法
通过建立工厂对象,向工厂对象传参获取实例。
工厂的创建方法一般用选择分支来创建实例。
不符合开闭原则。 添加新产品需要修改工厂创建方法。 而且产品过多代码十分臃肿。
1 2 3 4 5 6 7 8 9
| public class Factory { public Factory(); public create(String type){ if(type == 'Item1') { return new Item1(); } else if ... } }
|
抽象工厂
对产品进行归类, 为一类产品单独创建工厂。
所有工厂继承一个抽象工厂接口。
可以为每一个产品建立对应工厂, 这样建立新产品时只实现新产品的工厂即可。符合开闭原则。
1 2 3 4 5 6 7 8 9 10
| public interface AbstractFactory{ public Item create(); }
public class Factory1 implements AbstractFactory{ public Factory1(); public Item create(); }
AbstractFactory factory = new Factory1();
|
单例模式
实现
类内定义私有静态对象(private static
), 隐藏构造方法(private
), 对外提供获取唯一实例的方法(getInstance()
) 。
单例对象初始为null
, 在第一次调用时创建(懒惰初始化)。
问题
多个线程同时调用可能会产生多个对象。
解决方法
同步方法(synchronized)
将获取唯一实例的方法用synchronized
修饰, 变成同步方法。
此方法会严重降低性能。
直接创建实例
在声明单例对象的同时创建实例, 而不是在第一次获取实例时创建。
此方法浪费资源。
双重检查加锁
首先检查实例是否已创建, 如果实例已被创建, 则不需要进行加锁。 若未被创建, 则加锁再次检查。
如此仍会出现一些问题。 具体实现时最好用局部变量传递。
1 2 3 4 5 6 7 8 9 10 11 12
| public Helper getHelper() { FinalWrapper<Helper> wrapper = helperWrapper; if (wrapper == null) { synchronized(this) { if (helperWrapper == null) { helperWrapper = new FinalWrapper<Helper>(new Helper()); } wrapper = helperWrapper; } } return wrapper.value; }
|
建造者模式
不同建造者实现建造者接口,来建造建筑。
通过监督来调用建造者。
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
| class Building{
void setBase(); }
interface Builder { void buildBase(); }
class Buidler1 implements Builder{ Building build1;
void buildBase() { build1.setBase(); } }
class Director{ Builder builder; void setBuilder(Builder builder) {
} void direct(){ builder.buildBase(); } }
Director director;
director.setBuilder(...); director.direct();
|
逐层进行封装。
原型模式
获取实例时不去从类进行实例化, 而是从已有实例克隆而来。
注意浅克隆和深克隆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Item implements Cloneable{ ...
Object x; void setObject(Object x);
@Override Item clone() throws CloneNotSupportedException { Item item = (Item)super.clone(); item.setObject(this.x.clone()); return item; } }
class ItemFactory{ private static Item protoType = new Item();
public static Item getInstance() { Item clone = protoType.clone(); return clone; } }
|
总结
抽象工厂和原型均是对象的创建。 抽象工厂解决多类别和扩展问题。 原型通过克隆解决大量对象的实时创建。
运用向上转型,通过接口创建对象的方法需要掌握。 在抽象工厂、建造者模式均有应用。
结构型模式
适配器模式
给无法对接的两个功能编写适配器进行对接。
如两项插头插三孔插座, 可以用一个带两孔插座、三相插头的插座进行适配。
遵循开闭原则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface TriplePin { void electrify(int l, int n, int e); } interface DualPin{ void electrify(int l, int n); }
class TV implements DualPin { void electrify(int l, int n); }
class Adapter implements TriplePin { DualPin dualPinDevice;
public void electrify(int l, int n, int e) { dualPinDevice.electrify(l, n); } }
|
装饰器模式
通过外层包裹来实现装饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Lipstick extends Decorator { public Lipstick(Showable showable) { super(showable); }
public void show() { ... showable.show(); ... } }
Showable madeupGirl = new Lipstick(new FoundationMakeup(new Girl()));
|
代理模式
通过建一个代理来执行目标类, 目的是改变其功能(如添加限制)。
比如为了过滤网站, 添加一个代理来进行过滤。
直接实现接口来实现代理并没有良好的扩展性。 一般通过实现JDK反射包中的InvocationHandler
接口来实现动态代理。
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
| public class KeywordFilter implements InvocationHandler { ...
private Object origin;
public KeywordFilter(Object origin) { this.origin = origin; ... }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ...
return method.invoke(origin, arg); } }
Internet internet = (Internet) Proxy.newProxyInstance( Modem.class.getClassLoader(), Modem.class.getInterfaces(), new KeywordFilter(new Modem()) );
|
外观模式
也称门面模式
。
给子系统进行包装, 仅为外部提供简单的必要的接口。
竞赛时经常使用, 将全过程拆解为几个过程, 在main
函数中调用, 而不在main
函数中具体实现。 这里main
函数便是门面。
桥接模式
采用注入的方式进行桥接。
如画图需要定义画笔颜色和形状。 可以向画笔注入形状形成图形。
如此可以实现任意颜色和形状的桥接。 不将颜色和形状耦合。
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
| public interface Ruler { public void regularize(); }
public class SquareRuler implements Ruler { @Override public void regularize() {
} }
public abstract class Pen { protected Ruler ruler; public Pen(Ruler ruler) { this.ruler = ruler; }
public abstract void draw(); }
public class BlackPen extends Pen { public BlackPen(Ruler ruler) { super(ruler); }
@Override public void draw() { } }
new BlackPen(new SquareRuler()).draw();
|
组合模式
用树形模型来表达部分/整体的层次结构。
如文件夹的组织结构便是组合模型。
其实就是树形模型。
享元模式
需要大量具有相似属性或行为(共享元)的不同对象时,可以提取共享元,然后用类似缓存池的方法防止大量创建对象而降低性能。
行为型模式
策略模式
将功能(策略)拆出来, 采用注入的方式应用到控制器。
如计算器, 可以有加减乘除等运算, 将运算分别实现一个运算接口包装成一个类, 然后注入到计算器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Addition implements Calculate { @Override public int calculate(int a, int b) { return a+b; } }
public class Calculator { private Calculate calculate; public void setClaculate(...) { ... } public int calc(int a, int b) { return this.calculate.calculate(a, b); } }
|
模板方法模式
父类分为实部和虚部, 实部所有子类共有, 虚部抽象, 根据子类需要实现。
观察者模式
这种情景一般有一个被观察对象和多个观察者, 观察者应实时记录被观察对象的状态。 观察者状态的更新不应通过不断访问被观察对象根据反馈来更新, 而应采取被观察对象更新时通知所有观察者的策略。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Shop { private String product; private List<Buyer> buyers; ... public void setProduct(String product) { this.product = product; notifyBuyers(); }
public void notifyBuyers() { buyers.stream().forEach(b -> b.inform()); } }
|
迭代器模式
定义规则来遍历聚合对象中的元素, 但不暴露聚合对象的内部表示。
及内部实现遍历顺序(队列、栈), 对外提供接口。
责任链模式
不同对象有不同的权限, 根据权限等级来将对象构建为有向图, 处理需求时进行遍历, 直到遇到有权限处理需求的人为止。
如公司报销业务, 员工、经理、CEO审批权限分别为1000,5000,10000. 可以用单向链表连接, 权限不够时则前往上一级。
根据实际情况, 结构不一定是链表, 还可能是有向树。
命令模式
将设备功能拆解为各种命令,对命令进行组合,最后绑定到控制器上。
将控制对象注入到命令中, 再将命令注入到控制器上, 将各部分彻底解耦。
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
| public class TV implements Device { public void on() { ... }
public void off() { ... } ... }
public class SwitchCommand implements Command { private Device device;
public SwitchCommand(Device device) { this.device = device; }
@Override public void exe() { device.on(); }
@Override public void unexe() { device.off(); } }
public class Controller { private Command okCommand; ...
public void bindOKCommand(Command okCommand) { this.okCommand = okCommand; } ...
public void buttonOKhold() { okCommand.exe(); } ... }
Controller controller = new Controller();
controller.bindOKCommand(new SwitchCommand(tv)); controller.buttonOKHold();
|
备忘录模式
通过备忘录来添加撤销功能。 类似Ctrl+Z
的功能。 备忘录不会暴露在外, 仅对外提供撤销功能。在执行文件内容修改的同时自动更新备忘录。
状态模式
根据状态来改变行为。
将状态分离出来, 控制器只记录当前状态和进行控制。 状态的转化在由状态执行。 相当于用状态构建一个图, 控制器会沿图来切换状态。
如下面Switcher
只管控制, 状态的切换由状态自身去执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Switcher { private State state = new Off(); public State getState() { ... } public void setState(State state) { ... } public void switchOn() { state.switchOn(this); } ... }
public class Off implements State { @Override public void switchOn(Switcher switcher) { switcher.setState(new On()); ... } }
|
访问者模式
一个基类有许多子类, 访问者与子类交流, 应当让子类去接受访问者来调用访问者的访问方法, 而不是访问者直接去访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public interface Acceptable{ public void accept(Visitor visitor); }
public class Candy extends Product implements Acceptable{ ... @Override public void accept(Visitor visitor) { visitor.visit(this); } }
for(Acceptable prodect : prodects) { product.accept(visitor); }
|
中介者模式
为需要联系的对象提供中介, 将网状结构改为星形结构。
如网络聊天室。
解释器模式
用于执行一系列命令。(如挂机脚本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Expression sequence = new Sequence(Arrays.asList( new Move(500, 600), new Repetition( new Sequence( Arrays.asList(new LeftClick(), new Delay(1)) ), 5 ), new RightDown(), new Delay(7200) ));
sequence.interpret();
|
参考资料
图解23种设计模式,不信你学不会!(建议收藏)
23 种设计模式详解(全23种)