【HeadFirst系列之HeadFirst设计模式】第4天之一杯咖啡,带你彻底搞懂装饰者模式!

Scroll Down

一杯咖啡,带你彻底搞懂装饰者模式!

导语:你是否曾经为代码的臃肿不堪而烦恼?你是否曾经为功能的扩展而绞尽脑汁?今天,我们就从一个简单的咖啡店点单系统开始,带你领略装饰者模式的魅力,让你的代码从此优雅而灵活!
4-装饰者模式之前言

一、从一个咖啡店的故事开始

想象一下,你经营着一家咖啡店,店里提供各种咖啡,比如浓缩咖啡(Espresso)、美式咖啡(HouseBlend)等等。顾客可以根据自己的喜好,添加各种调料,比如牛奶(Milk)、摩卡(Mocha)等等。

问题来了:如何设计一个系统,能够方便地计算不同咖啡和调料组合的价格呢?

最直观的想法:为每一种咖啡和调料的组合创建一个类,比如 EspressoWithMilk、HouseBlendWithMochaAndMilk 等等。但是,这种设计方式存在明显的缺陷:

  • 类爆炸:随着咖啡和调料种类的增加,类的数量会呈指数级增长,最终导致代码臃肿不堪。
  • 难以维护:如果需要修改某种咖啡或调料的价格,就需要修改所有相关的类,维护成本极高。
  • 难以扩展:如果需要添加新的咖啡或调料,就需要创建大量的新类,扩展性极差。

4-装饰者模式之星巴兹的故事-2

二、装饰者模式登场

装饰者模式(Decorator Pattern)就是为了解决上述问题而生的。它允许我们动态地给一个对象添加一些额外的职责,而不会影响其他对象。

让我们用装饰者模式来重新设计咖啡店的点单系统:

  1. 定义抽象组件(Component)Beverage 类,表示所有咖啡的基类,包含描述和价格两个属性。
  2. 定义具体组件(ConcreteComponent)EspressoHouseBlend 等类,继承自 Beverage,表示具体的咖啡种类。
  3. 定义抽象装饰者(Decorator)CondimentDecorator 类,继承自 Beverage,并持有一个 Beverage 对象的引用。
  4. 定义具体装饰者(ConcreteDecorator)MilkMocha 等类,继承自 CondimentDecorator,表示具体的调料。
    4-装饰者模式之装饰者的特性-1

代码示例:

// 抽象组件
public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

// 具体组件
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }

    public double cost() {
        return 1.99;
    }
}

// 抽象装饰者
public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    public abstract String getDescription();
}

// 具体装饰者
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    public double cost() {
        return .20 + beverage.cost();
    }
}

使用装饰者模式:

Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());

beverage = new Mocha(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());

输出结果:

Espresso $1.99
Espresso, Mocha $2.19

三、装饰者模式的优势

  • 灵活扩展:可以动态地添加或移除功能,而无需修改原有代码。
  • 避免类爆炸:无需为每个功能组合创建子类,代码更加简洁。
  • 符合开闭原则:对扩展开放,对修改关闭,提高了代码的可维护性和可扩展性。

四、装饰者模式的应用场景

装饰者模式在实际开发中有着广泛的应用,例如:

  • Java I/O 流BufferedInputStreamDataInputStream 等都是装饰者模式的典型应用。
    4-装饰者模式之JavaIO中的装饰者-1
    4-装饰者模式之JavaIO中的装饰者-2
    4-装饰者模式之编写自己的JavaIO装饰者-1
    4-装饰者模式之编写自己的JavaIO装饰者-2
  • 日志记录:可以为日志记录器添加不同的功能,比如日志级别过滤、日志格式化等。
  • 权限控制:可以为用户添加不同的权限,比如管理员权限、普通用户权限等。

五、总结

装饰者模式是一种非常实用的设计模式,它可以帮助我们编写出更加灵活、可扩展、易维护的代码。希望通过这篇文章,你能对装饰者模式有一个更深入的理解,并能在实际开发中灵活运用它。

最后,留一个思考题: 除了咖啡店的例子,你还能想到哪些场景可以使用装饰者模式?欢迎在评论区留言分享你的想法!

关注公众号,获取更多精彩内容!
个人公众号名片
(本文参考《Head First 设计模式》第3章)