美文网首页
装饰者模式

装饰者模式

作者: 字字经心 | 来源:发表于2022-07-26 15:20 被阅读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. 真正处理请求的函数(接收者)

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

    相关文章

      网友评论

          本文标题:装饰者模式

          本文链接:https://www.haomeiwen.com/subject/lprsirtx.html