装饰者模式和命令模式

装饰者模式

现在有一家咖啡连锁店,咖啡如A、B、C、D多种,配料更是有蒸奶(Milk)、豆浆(Soy)、摩卡(Mocha)、奶泡(whip)等,咖啡店会根据加入的配料收取不同的费用。那怎样设计其中的单订系统呢,起初是这样设计的。看类图

duck1

其中

  1. Beverage 是各饮料的基类,里面包含变量 描述,各个配料的布尔值,cost 计算配料的价格(该方法是具体的)
  2. 子类饮料 BeverageA,设置加入制定配料,重写cost() 方法,并调用父类的cost 计算配料钱,再加上饮料费用,计算出总额。

看起来4 个类就可以解决问题,但是一旦需求变动

  1. 调料的价格变动,要修改父类Berverage 代码
  2. 一旦出现新的饮料,得为Berverage 加成员变量
  3. 喝个冰茶,还得设置其他调料为false,真是麻烦
  4. 有人来个 双倍 Milk 调料,怎么办?

我们对类的设计,没有做到对扩展开发,对修改关闭(开发——关闭原则)。我们的目标是允许类容易扩展,在不修改现有代码的情况下就可搭配新的行为,弹性应对改变的需求

做到开放-关闭 原则,如观察者模式。不需要每一部分都这样设计,要花费很多时间,关注在设计中最有可能改变的地方应用开放-关闭原则。

如果现在有这样一种设计来实现上面的需求,如下

duck1

实现过程

  1. BerverA 是被装饰者,cost() 返回BerverA 的费用
  2. Mocha 是装饰者,cost() 调用BerverA cost (),返回Mocha、BerverageA 总费用
  3. whip 是装饰者,cost调用Mocha cost(),返回Whip、Mocha、BerverageA 总费用

其中饮料BerverA 继承Berver 饮料基类,Mocah/Whip 继承了装饰者基类(该类已继承Berverage ),类图如下

duck1

调料装饰者,除了实现cost之外,还实现了getDescription(),现在看具体代码实现

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
// 组件 饮料 Berverage
public abstract class Berverage{
String description = 'Unkown Berverage';

public String getDescription(){
return description;
}

public abstract double cost();
}

// 装饰者抽象类 调料
public abstract class CondimentDecorator extends Beverage {
// 所有调料装饰者 重新实现 getDescription
public abastract String getDescription();
}

// 具体组件 饮料 BeverageA
public class BerverageA extends Beverage {
public BerverageA() {
// 该变量来自父类
description = 'BeverageA';
}

public double cost() {
return 3.99;
}
}

// 饮料 BeverageB
public class BeverageB extends Beverage {
public BeverageB() {
// 该变量来自父类
description = 'BeverageB';
}

public double cost() {
return 2.29;
}
}

// 具体装饰者 调料 摩卡(mocha)
public class Mocha extends CondimentDcorator {
Beverage beverage;

// 传入 被装饰者 或者 其他装饰者,利用多态的特性
public Mocha(Beverage beverage) {
this.beverage = beverage;
}

// 重新实现 getDescription 可以连调料都输出
public String getDescription() {
return beverage.getDescription + ', Mocha';
}

public double cost(){
return 1.11 + beverage.cost();
}
}

// 具体装饰者 调料 蒸奶(Milk)
public class Milk extends CondimentDcorator {
Beverage beverage;

// 传入 被装饰者 或者 其他装饰者,利用多态的特性
public Milk(Beverage beverage) {
this.beverage = beverage;
}

// 重新实现 getDescription 可以连调料都输出
public String getDescription() {
return beverage.getDescription + ', Milk';
}

public double cost(){
return 13.11 + beverage.cost();
}
}

就是这样实现装饰者模式的,接下来我们测试一下

1
2
3
4
5
6
7
8
9
10
11
12
public class Test{
public static void main(String args[]){
Beverage beverage = new BeverageA();
// 加入调料 摩卡(mocha)
beverage = new Mocha(beverage);
// 双倍 摩卡(mocha) 哈哈!
beverage = new Mocha(beverage);
// 加入 蒸奶(Milk)
beverage = new Milk(beverage);
System.out.println(beverage.getDescription() + '价格,' + beverage.cost());
}
}

以上就是装饰者模式的全部实现了,其定义是,装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承共有弹性的替代方案。

java.io 类

duck1

java.io 包内的类太多了,其中很多类都是装饰者,如图用装饰者将功能结合起来以读取文件数据,

  1. BufferedInputStream 是一个具体的装饰者,加入缓冲输入改进性能;用realine() 一次读取一行文件增强接口
  2. LineNumberInputStream 也是一个具体的装饰者,加上了计算行数的能力。

duck1

命令模式

让我们从餐厅订单的工作流程来认识命令模式,顾客在订单上写好菜单,把订单交给女招待;女招待拿了订单就放在订单柜台,然后喊了一句 “单订来了!”;最后厨师就根据单订准备餐点。详细分析过程就是,

  1. 顾客知道自己想要什么,createOrder() 创建一份订单
  2. 女招待利用 takeOrder(),拿到订单并且调用 orderUp() 方法通知厨师开始准备餐点
  3. 厨师根据指令准备餐点

其中以上过程,创建的单订对象里面包含厨师(命令接收者)和告诉厨师的一系列指令,并对外只暴露一个orderUp()通知执行命令;女招待(调用者)根本不关心谁是命令接收者,厨师也不关心被谁触发命令,被谁调用。

我们用遥控器遥控灯泡亮暗的例子来观察代码实现,当我们按下遥控器(调用者)亮灯按钮(命令对象)时,灯(接收者)亮了。命令对象包含了接受者和对接受者的指令,让所有的命令对象实现相同的包含一个方法的接口,在餐厅例子中叫orderUp,一般惯用execute

1
2
3
public interface Command {
public void execute();
}

现在实现打开电灯的命令,接受者Light 有on() 和 off()

1
2
3
4
5
6
7
8
9
10
11
12
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light){
this.light = light;
}

// 对外暴露 execute 给调用者
public void execute(){
light.on();
}
}

使用命令对象,假设我们有个遥控器,按下按钮对应的命令对象插槽

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
public class SimpleRemoteControl {
Command slot;

public SimpleRemoteControl(){}

public void setCommand(Command command){
slot = command;
}

public void buttonWasPressed(){
slot.execute();
}
}

// Test
public class RemoteControlTest {
public static void main(String[] args){
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand ligthOn = new LightOnComand(light);

remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}

以上就是用命令模式实现按下遥控器按钮,下达命令使灯亮的全部代码。命令模式将“请求”封装成对象,以便使用不同的请求、队列来参数化其他对象。

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象中,这个对象只暴露出一个execute() 方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了那些动作,只知道如果调用execute()方法,请求的目的就能达到。命令模式类图

duck1

laravel 消息队列

现有一个日志收集服务系统要接收来自数据埋点的数据,利用laravel 的消息队列来处理请求。处理过程是利用controller 先把真正处理请求函数和数据联系起来,再启动work 进程消费这些任务,最终完成数据入库。利用命令模式来理解这样过程

  1. controller(客户),创建命令对象,把真正处理请求的函数(接受者)和参数用json 字符串保存到redis/mysql
  2. work 进程(调用者),可用supervisord 管理启动。读取redis 中命令对象,调用接收者完成任务
  3. 真正处理请求的函数(接收者)

欢迎大家给我留言,提建议,指出错误,一起讨论学习技术的感受!

-------------本文结束感谢您的阅读-------------
Donate comment here