装饰者模式

2022-07-26  本文已影响0人  字字经心

装饰者模式

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

decorate1.png

其中

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

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

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

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

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

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

decorate2.png

实现过程

  1. BerverA 是被装饰者,cost() 返回BerverA 的费用
  2. Mocha 是装饰者,cost() 调用BerverA cost (),返回Mocha、BerverageA 总费用
  3. 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.png

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

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

命令模式

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

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

其中以上过程,创建的单订对象里面包含厨师(命令接收者)和告诉厨师的一系列指令,并对外只暴露一个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.png

laravel 消息队列

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

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

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

上一篇下一篇

猜你喜欢

热点阅读