手撕设计模式——咖啡点单系统之装饰模式
1.业务需求
大家好,我是菠菜啊,好久不见,今天给大家带来的是——装饰模式。老规矩,在介绍这期内容前,我们先来看看这样的需求:现在有一个咖啡馆,有基础饮料:美式咖啡、红茶、拿铁等,配料:牛奶、奶泡、糖等,怎么样实现任意的基础饮料和配料的组合,并且能够输出组合描述以及结算金额?
2.代码实现
Talk is cheap,show me your code.
初版实现思路:
我们之前学习过桥接模式,可以用该模式实现。
初版代码如下:
//饮料抽象类
public abstract class Beverage2 {
protected ToppingImplementor toppingImplementor;
protected String description;
protected double cost;
public Beverage2(ToppingImplementor toppingImplementor)
{
this.toppingImplementor = toppingImplementor;
}
public void setToppingImplementor(ToppingImplementor toppingImplementor) {
this.toppingImplementor = toppingImplementor;
}
public String getDescription(){
return this.description+toppingImplementor.addTopping();
}
public double cost(){
return this.cost+toppingImplementor.addCost();
}
}
//配料接口
public interface ToppingImplementor {
String addTopping();
double addCost();
}
//红茶
public class BlackTea2 extends Beverage2{
public BlackTea2(ToppingImplementor toppingImplementor) {
super(toppingImplementor);
description = "红茶";
cost = 12;
}
}
//美式
public class AmericanoCoffee2 extends Beverage2{
public AmericanoCoffee2(ToppingImplementor toppingImplementor) {
super(toppingImplementor);
description = "美式咖啡";
cost = 18;
}
}
//拿铁
public class Latte2 extends Beverage2{
public Latte2(ToppingImplementor toppingImplementor) {
super(toppingImplementor);
description = "拿铁";
cost = 17;
}
}
//牛奶配料
public class Milk2 implements ToppingImplementor{
@Override
public String addTopping() {
return "加奶";
}
@Override
public double addCost() {
return 3;
}
}
//糖配料
public class Sugar2 implements ToppingImplementor{
@Override
public String addTopping() {
return "加糖";
}
@Override
public double addCost() {
return 2;
}
}
//奶泡配料
public class Whip2 implements ToppingImplementor{
@Override
public String addTopping() {
return "加奶泡";
}
@Override
public double addCost() {
return 4;
}
}
//客户端
public class Client2 {
public static void main(String[] args) {
Beverage2 beverage2 = new Latte2(new Sugar2());
System.out.println(beverage2.getDescription()+"花费:"+beverage2.cost());
beverage2.setToppingImplementor(new Milk2());
System.out.println(beverage2.getDescription()+"花费:"+beverage2.cost());
}
}
执行结果:
代码结构:
思考:
上述代码用桥接模式将基础饮料和配料的抽象和实现分离,但是我们也发现一些问题。比如,我们运行时要加双份配料无法实现,如果再添加一种维度(如杯型)无需要修改代码才能实现。于是,我们进一步优化代码。
3.代码优化
优化代码:
//饮料基类
public abstract class Beverage {
abstract String getDescription();
abstract double cost();
}
//基础饮料具体实现
public class BlackTea extends Beverage{
@Override
public String getDescription() {
return "红茶";
}
@Override
public double cost() {
return 12;
}
}
public class AmericanoCoffee extends Beverage{
@Override
public String getDescription() {
return "美式咖啡";
}
@Override
public double cost() {
return 18;
}
}
public class Latte extends Beverage{
@Override
public String getDescription() {
return "拿铁";
}
@Override
public double cost() {
return 17;
}
}
//配料装饰器抽象类
public abstract class BeverageDecorator extends Beverage{
protected Beverage beverage;
public BeverageDecorator(Beverage beverage){
this.beverage = beverage;
}
}
//具体配料装饰器实现
public class Milk extends BeverageDecorator{
public Milk(Beverage beverage) {
super(beverage);
}
@Override
String getDescription() {
return beverage.getDescription() + "加奶";
}
@Override
double cost() {
return beverage.cost() + 3;
}
}
public class Sugar extends BeverageDecorator{
public Sugar(Beverage beverage) {
super(beverage);
}
@Override
String getDescription() {
return beverage.getDescription() + "加糖";
}
@Override
double cost() {
return beverage.cost() + 2;
}
}
public class Whip extends BeverageDecorator{
public Whip(Beverage beverage) {
super(beverage);
}
@Override
String getDescription() {
return beverage.getDescription() + "加奶泡";
}
@Override
double cost() {
return beverage.cost() + 4;
}
}
//客户端
public class Client {
public static void main(String[] args) {
Beverage beverage = new Sugar(new Sugar(new Milk(new AmericanoCoffee())));
System.out.println(beverage.getDescription()+",价格"+beverage.cost());
Beverage beverage2 = new Latte();
beverage2 = new Milk(beverage2);
beverage2 = new Whip(beverage2);
beverage2 = new Sugar(beverage2);
System.out.println(beverage2.getDescription()+",价格"+beverage2.cost());
}
}
执行结果:
代码结构:
思考:
上述代码将配料种类和基础饮料解耦,N种饮料+M种配料只需(N+M)个类,支持运行时自由组合配料(如双倍加糖),新增配料或者基础饮料无需修改代码,满足开闭原则。这种纵向增强现有功能,层层叠加功能的实现方式,就是装饰模式。
4.定义与组成
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许动态地向对象添加新功能,同时不改变其结构。该模式通过创建包装对象(装饰器)来扩展原始对象的功能,提供了比继承更灵活的功能扩展方式。
核心定义:
在不改变现有对象结构的情况下,动态地给对象添加额外职责。装饰模式比生成子类更为灵活。
- 组件接口(Component ):定义一个对象接口,给这些动态地添加职责
- 具体组件(Concrete Component):实现组件接口的基础功能
- 抽象装饰器(Decorator):维持对组件对象的引用并实现组件接口
- 具体装饰器(Concrete Decorator):添加具体的附加功能
5.应用示例
5.1 Java I/O流体系
// 经典装饰模式实现
InputStream fileStream = new FileInputStream("data.txt");
InputStream bufferedStream = new BufferedInputStream(fileStream); //添加缓冲
InputStream gzipStream = new GZIPInputStream(bufferedStream);
- 组件接口:
InputStream
- 具体组件:
FileInputStream
- 装饰器抽象类:
FilterInputStream
- 具体装饰器:
BufferedInputStream
,GZIPInputStream
5.2 Java GUI (Swing/AWT)
// 为组件添加滚动功能
JTextArea textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
5.3 服务层功能增强
日志/监控/权限校验:通过装饰器为业务逻辑添加非核心功能,保持业务类纯净
UserService service = new UserServiceImpl();
service = new LoggingDecorator(service); // 添加日志记录
service = new TimingDecorator(service); // 添加性能监控
5.4 动态配置组合
电商优惠系统:基础价格策略通过装饰器叠加满减、折扣券等功能
PriceStrategy base = new BasePriceStrategy();
base = new DiscountDecorator(base, 0.8); // 8折
base = new CouponDecorator(base, 100); // 满送100
6.适用场景
6.1 核心适用场景
✅ 需动态扩展功能:如运行时按需添加日志、加密等。
✅ 避免类爆炸:功能组合多时(如咖啡配料、电商优惠)。
✅ 保持接口一致性:所有对象(原始/装饰后)对外暴露相同接口。
❌ 基础功能修改:如需彻底改变核心逻辑,应使用策略模式或适配器模式。
6.2 注意事项
- 避免过度装饰
装饰层数过多会降低可读性和调试难度(如10层装饰调用栈深度增加)
- 顺序敏感:
装饰顺序影响最终结果
7.结构性模式对比
装饰器、代理、桥接 都属于结构型模式,都涉及对象组合,但它们解决的问题、目的和实现方式有显著区别:
特性 | 装饰器模式 (Decorator) | 代理模式 (Proxy) | 桥接模式 (Bridge) |
---|---|---|---|
主要目的 | 动态添加职责 (增强功能) | 控制访问 (间接访问、延迟加载等) | 分离抽象与实现 (解耦两个维度) |
关注点 | 为对象添加新功能/行为 | 管理对对象的访问方式 | 管理类结构的扩展维度 |
关系对象 | 包装同一接口的对象 (通常层次深) | 代表一个具体对象 (通常1对1) | 连接抽象角色和实现角色 |
功能变化 | 运行时动态添加/移除功能 | 隐藏/控制原有功能 | 编译时确定抽象和实现的组合 |
继承替代 | 是 (避免子类爆炸) | 不一定是 (也可用于控制) | 是 (防止多维度继承爆炸) |
UML 关键 | 装饰器与被装饰者实现同一接口 | 代理与真实对象实现同一接口 | 抽象持有实现的接口引用 |
典型应用 | Java I/O 流、GUI 组件增强 | 虚拟代理、保护代理、远程代理 | 跨平台UI库、驱动接口 |
一句话总结:“加功能(装饰)、控访问(代理)、解耦合(桥接)”。
8.总结
装饰模式是Java开发中最常用且强大的设计模式之一,特别适用于需要动态扩展功能的场景。它通过组合代替继承,完美解决了功能扩展中的类爆炸问题,同时保持了对开闭原则的遵守。
使用装饰模式当:
- 需要在不影响其他对象的情况下添加功能
- 需要动态、透明地添加或撤销功能
- 继承扩展不可行或不实际(如final类)
- 系统需要多层次的功能组合
技术需要沉淀,同样生活也是~
个人链接:博客,欢迎一起交流