软件设计模式

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种)