手撕设计模式——咖啡点单系统之装饰模式

手撕设计模式——咖啡点单系统之装饰模式

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());

    }
}

执行结果:

装饰模式之初版代码执行结果

代码结构:

装饰模式之初版代码UML.drawio

思考:

​ 上述代码用桥接模式将基础饮料和配料的抽象和实现分离,但是我们也发现一些问题。比如,我们运行时要加双份配料无法实现,如果再添加一种维度(如杯型)无需要修改代码才能实现。于是,我们进一步优化代码。

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());

    }
}

执行结果:

装饰模式之优化代码执行结果

代码结构:

装饰模式之优化代码UML.drawio

思考:

​ 上述代码将配料种类和基础饮料解耦,N种饮料+M种配料只需(N+M)个类,支持运行时自由组合配料(如双倍加糖),新增配料或者基础饮料无需修改代码,满足开闭原则。这种纵向增强现有功能,层层叠加功能的实现方式,就是装饰模式

装饰模式之层层嵌套示意图

4.定义与组成

装饰模式UML.drawio

​ 装饰模式(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类)
  • 系统需要多层次的功能组合

技术需要沉淀,同样生活也是~
个人链接:博客,欢迎一起交流

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇