GoF23:Decorator-装饰者
1、装饰者模式
装饰者(Decorator):
比继承更有弹性的替代方案。
- 场景:在不改变原有类的情况下,动态增强对象的功能。
- 说明:
- 遵循开闭原则。
- 装饰者和被装饰者有相同超类型。
- 具体构件可以单独使用,也可以被任意个装饰者包装(套娃)。
- 装饰者可以在被装饰者的行为前后添加功能,以达到增强的效果。
类图
- Component(抽象构件):作为公共接口。
- ConcreteComponent(具体构件):要动态增强的对象。
- Decorator(抽象装饰者):与具体构件实现同一个接口,持有对 Component 的引用。
- ConcreteDecorator(具体装饰者):可以添加新的状态或方法,用于增强具体构件。
应用:Java I/O
过滤流 = 节点流的装饰者
2、case:咖啡 & 调料
2.1、需求
咖啡可以单独销售、加入调料销售
费用 = 咖啡单价 + 调料
- 咖啡:
- HouseBlend:首选咖啡 $0.89
- DarkRoast:深焙咖啡 $0.99
- Decaf:脱因咖啡 $1.05
- Espresso:浓咖啡 $1.99
- 调料
- Milk:蒸奶 $0.1
- Soy:豆浆 $0.2
- Mocha:摩卡 $0.15
- Whip:奶泡 $0.1
2.2、bad 方案
以下是两个不好的方案,用于引入装饰者模式。
所有咖啡类型
创建所有可能的咖啡类型
-
单品类:4 种
-
调料类:咖啡与调料的任意组合情况,有很多种。
分析
- 违反原则:
- 合成复用:多用组合或聚合,少用继承。
- 依赖倒置:面向抽象编程。
- 可维护性差:若需求变化,可能导致现有代码的大量修改,或增加/减少大量类。
抽象单品 + 调料变量
基于调料变量的布尔值,体现不同搭配组合。
-
抽象单品类:作为具体咖啡的超类。
- 成员变量:调料的布尔值,代表是否添加。
- 方法:cost() 抽象方法,由子类实现逻辑。
-
子类:继承抽象类,由布尔值体现不同的搭配。
分析
- 类的维护性差:若需求变化,可能导致现有代码的大量修改,或增加/减少大量类。
- 冗余属性:子类继承了父类的所有属性,尽管它们可能不需要用到。
2.3、实现装饰者模式
设计
-
抽象构件:抽象咖啡类,作为公共接口。
-
具体构件:具体的咖啡类,继承自抽象构件。
-
抽象装饰者:抽象调料,继承自抽象构件。
-
装饰者:调料。
实现
抽象构件 - Coffee
-
成员变量:description,描述咖啡类型。
-
方法:
-
getDescription():获取咖啡描述;
-
cost():抽象方法,计算咖啡价格。
public abstract class Coffee { protected String description; public String getDescription() { return description; } public abstract double cost(); }
-
具体构件
-
构造器初始化 description
-
实现 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
-
继承:继承抽象组件,保证类型匹配。
-
组合:引用一个抽象组件,代表被装饰者。
public abstract class CondimentDecorator extends Coffee { protected Coffee coffee; @Override public abstract String getDescription(); }
具体装饰者
继承抽象装饰者
-
依赖注入:可能是具体构件或另一个装饰者。
-
重写 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、继承 & 组合
装饰者模式同时使用了继承和组合。
- 继承:目的是类型匹配,使装饰者可以替代具体构件的使用。
- 组合:目的是获取行为,使装饰者可以获取并增强具体构件的行为。
3.2、设计模式
具体装饰者的创建
- new:面向实现编程。
- 工厂、生成器模式:👍
装饰者 vs 代理
装饰者 | 代理 | |
---|---|---|
定义 | 在不改变原有类的情况下,动态增强对象的功能 | 给某个对象提供一个代理,以控制对该对象的访问 |
侧重点 | 增强对象,继承的替代方案 | 控制访问,隐藏真实主题 |
增强 | 增强对象本身的功能,使其更强大 | 可以增加一些功能,通常与自身业务无关 |