GoF23:Decorator-装饰者

1、装饰者模式

装饰者(Decorator)

比继承更有弹性的替代方案

  1. 场景:在不改变原有类的情况下,动态增强对象的功能
  2. 说明
    1. 遵循开闭原则。
    2. 装饰者和被装饰者有相同超类型
    3. 具体构件可以单独使用,也可以被任意个装饰者包装(套娃)。
    4. 装饰者可以在被装饰者的行为前后添加功能,以达到增强的效果。

类图

  1. Component(抽象构件):作为公共接口。
  2. ConcreteComponent(具体构件):要动态增强的对象。
  3. Decorator(抽象装饰者):与具体构件实现同一个接口,持有对 Component 的引用。
  4. ConcreteDecorator(具体装饰者):可以添加新的状态或方法,用于增强具体构件。

image-20220127235219535

应用Java I/O

过滤流 = 节点流的装饰者

image-20220128145141902

2、case:咖啡 & 调料

2.1、需求

咖啡可以单独销售、加入调料销售

费用 = 咖啡单价 + 调料

  1. 咖啡
    • HouseBlend:首选咖啡 $0.89
    • DarkRoast:深焙咖啡 $0.99
    • Decaf:脱因咖啡 $1.05
    • Espresso:浓咖啡 $1.99
  2. 调料
    • Milk:蒸奶 $0.1
    • Soy:豆浆 $0.2
    • Mocha:摩卡 $0.15
    • Whip:奶泡 $0.1

2.2、bad 方案

以下是两个不好的方案,用于引入装饰者模式。

所有咖啡类型

创建所有可能的咖啡类型

  1. 单品类:4 种

  2. 调料类:咖啡与调料的任意组合情况,有很多种。

    image-20220127183441314

分析

  1. 违反原则
    • 合成复用:多用组合或聚合,少用继承。
    • 依赖倒置:面向抽象编程。
  2. 可维护性差:若需求变化,可能导致现有代码的大量修改,或增加/减少大量类。

抽象单品 + 调料变量

基于调料变量的布尔值,体现不同搭配组合。

  1. 抽象单品类:作为具体咖啡的超类。

    1. 成员变量:调料的布尔值,代表是否添加。
    2. 方法:cost() 抽象方法,由子类实现逻辑。
  2. 子类:继承抽象类,由布尔值体现不同的搭配。

    image-20220127235534547

分析

  1. 类的维护性差:若需求变化,可能导致现有代码的大量修改,或增加/减少大量类。
  2. 冗余属性:子类继承了父类的所有属性,尽管它们可能不需要用到。

2.3、实现装饰者模式

设计

  1. 抽象构件:抽象咖啡类,作为公共接口。

  2. 具体构件:具体的咖啡类,继承自抽象构件。

  3. 抽象装饰者:抽象调料,继承自抽象构件。

  4. 装饰者:调料。

    image-20220127235800772

实现

抽象构件 - Coffee

  1. 成员变量:description,描述咖啡类型。

  2. 方法

    1. getDescription():获取咖啡描述;

    2. cost()抽象方法,计算咖啡价格。

      public abstract class Coffee {
          protected String description;
      
          public String getDescription() {
              return description;
          }
      
          public abstract double cost();
      }
      

具体构件

  1. 构造器初始化 description

  2. 实现 cost(),返回咖啡单品价格。

    // 首选咖啡
    public class HouseBlend extends Coffee {
        public HouseBlend() {
            description = "HouseBlend Coffee";
        }
    
        @Override
        public double cost() {
            return 0.89;
        }
    }
    
    // 深焙咖啡
    public class DarkRoast extends Coffee {
        public DarkRoast() {
            description = "Dark Roast Coffee";
        }
    
        @Override
        public double cost() {
            return 0.99;
        }
    }
    

抽象装饰者 - Condiment

  1. 继承:继承抽象组件,保证类型匹配。

  2. 组合引用一个抽象组件,代表被装饰者。

    public abstract class CondimentDecorator extends Coffee {
        protected Coffee coffee;
    
        @Override
        public abstract String getDescription();
    }
    

具体装饰者

继承抽象装饰者

  1. 依赖注入:可能是具体构件或另一个装饰者。

  2. 重写 cost():添加当前调料的费用。

    // 摩卡
    public class Mocha extends CondimentDecorator {
        public Mocha(Coffee coffee) {
            this.coffee = coffee;
        }
    
        @Override
        public double cost() {
            return coffee.cost() + 0.2;
        }
    
        @Override
        public String getDescription() {
            return coffee.getDescription() + ", Mocha";
        }
    }
    // 奶泡
    public class Whip extends CondimentDecorator {
        public Whip(Coffee coffee) {
            this.coffee = coffee;
        }
    
        @Override
        public double cost() {
            return coffee.cost() + 0.1;
        }
    
        @Override
        public String getDescription() {
            return coffee.getDescription() + ", Whip";
        }
    }
    

客户端

示例:创建一杯双倍摩卡奶泡首选咖啡

// 首选咖啡
Coffee coffee = new HouseBlend();

// 双倍摩卡
coffee = new Mocha(coffee);
coffee = new Mocha(coffee);

// 奶泡
coffee = new Whip(coffee);

System.out.println(coffee.cost());

3、说明

3.1、继承 & 组合

装饰者模式同时使用了继承和组合。

  1. 继承:目的是类型匹配,使装饰者可以替代具体构件的使用。
  2. 组合:目的是获取行为,使装饰者可以获取并增强具体构件的行为。

3.2、设计模式

具体装饰者的创建

  1. new:面向实现编程。
  2. 工厂、生成器模式:👍

装饰者 vs 代理

装饰者 代理
定义 在不改变原有类的情况下,动态增强对象的功能 给某个对象提供一个代理,以控制对该对象的访问
侧重点 增强对象,继承的替代方案 控制访问,隐藏真实主题
增强 增强对象本身的功能,使其更强大 可以增加一些功能,通常与自身业务无关
posted @ 2022-01-23 16:16  Jaywee  阅读(55)  评论(0编辑  收藏  举报

👇