装饰者模式
装饰者模式
现在有一家咖啡连锁店,咖啡如A、B、C、D多种,配料更是有蒸奶(Milk)、豆浆(Soy)、摩卡(Mocha)、奶泡(whip)等,咖啡店会根据加入的配料收取不同的费用。那怎样设计其中的单订系统呢,起初是这样设计的。看类图
decorate1.png其中
- Beverage 是各饮料的基类,里面包含变量 描述,各个配料的布尔值,cost 计算配料的价格(该方法是具体的)
- 子类饮料 BeverageA,设置加入制定配料,重写cost() 方法,并调用父类的cost 计算配料钱,再加上饮料费用,计算出总额。
看起来4 个类就可以解决问题,但是一旦需求变动
- 调料的价格变动,要修改父类Berverage 代码
- 一旦出现新的饮料,得为Berverage 加成员变量
- 喝个冰茶,还得设置其他调料为false,真是麻烦
- 有人来个 双倍 Milk 调料,怎么办?
我们对类的设计,没有做到对扩展开发,对修改关闭(开发——关闭原则)。我们的目标是允许类容易扩展,在不修改现有代码的情况下就可搭配新的行为,弹性应对改变的需求。
做到开放-关闭 原则,如观察者模式。不需要每一部分都这样设计,要花费很多时间,关注在设计中最有可能改变的地方应用开放-关闭原则。
如果现在有这样一种设计来实现上面的需求,如下
decorate2.png实现过程
- BerverA 是被装饰者,cost() 返回BerverA 的费用
- Mocha 是装饰者,cost() 调用BerverA cost (),返回Mocha、BerverageA 总费用
- whip 是装饰者,cost调用Mocha cost(),返回Whip、Mocha、BerverageA 总费用
其中饮料BerverA 继承Berver 饮料基类,Mocah/Whip 继承了装饰者基类(该类已继承Berverage ),类图如下
decorate3.png调料装饰者,除了实现cost之外,还实现了getDescription(),现在看具体代码实现
// 组件 饮料 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();
}
}
就是这样实现装饰者模式的,接下来我们测试一下
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 类
decorate4.pngjava.io 包内的类太多了,其中很多类都是装饰者,如图用装饰者将功能结合起来以读取文件数据,
- BufferedInputStream 是一个具体的装饰者,加入缓冲输入改进性能;用realine() 一次读取一行文件增强接口
- LineNumberInputStream 也是一个具体的装饰者,加上了计算行数的能力。
命令模式
让我们从餐厅订单的工作流程来认识命令模式,顾客在订单上写好菜单,把订单交给女招待;女招待拿了订单就放在订单柜台,然后喊了一句 “单订来了!”;最后厨师就根据单订准备餐点。详细分析过程就是,
- 顾客知道自己想要什么,createOrder() 创建一份订单
- 女招待利用 takeOrder(),拿到订单并且调用 orderUp() 方法通知厨师开始准备餐点
- 厨师根据指令准备餐点
其中以上过程,创建的单订对象里面包含厨师(命令接收者)和告诉厨师的一系列指令,并对外只暴露一个orderUp()通知执行命令;女招待(调用者)根本不关心谁是命令接收者,厨师也不关心被谁触发命令,被谁调用。
我们用遥控器遥控灯泡亮暗的例子来观察代码实现,当我们按下遥控器(调用者)亮灯按钮(命令对象)时,灯(接收者)亮了。命令对象包含了接受者和对接受者的指令,让所有的命令对象实现相同的包含一个方法的接口,在餐厅例子中叫orderUp,一般惯用execute
public interface Command {
public void execute();
}
现在实现打开电灯的命令,接受者Light 有on() 和 off()
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light){
this.light = light;
}
// 对外暴露 execute 给调用者
public void execute(){
light.on();
}
}
使用命令对象,假设我们有个遥控器,按下按钮对应的命令对象插槽
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()方法,请求的目的就能达到。命令模式类图
Command1.pnglaravel 消息队列
现有一个日志收集服务系统要接收来自数据埋点的数据,利用laravel 的消息队列来处理请求。处理过程是利用controller 先把真正处理请求函数和数据联系起来,再启动work 进程消费这些任务,最终完成数据入库。利用命令模式来理解这样过程
- controller(客户),创建命令对象,把真正处理请求的函数(接受者)和参数用json 字符串保存到redis/mysql
- work 进程(调用者),可用supervisord 管理启动。读取redis 中命令对象,调用接收者完成任务
- 真正处理请求的函数(接收者)
欢迎大家给我留言,提建议,指出错误,一起讨论学习技术的感受!