JavaDog程序狗
发布于 2026-11-10 / 7 阅读
0
0

【设计模式】跟着狗哥复习设计模式(九):装饰器模式

拿咖啡店举个例子。

一杯美式 10 元,加牛奶 2 元,加糖 1 元。有人只加牛奶,也有人两样都要。

搭配不复杂,但很适合拿来理解装饰器模式。

加瓦狗给咖啡逐层加入牛奶和糖

每种搭配都写一个类,有点顶不住

刚开始可能只有 Americano,后来又有牛奶美式、加糖美式、牛奶加糖美式。

继续加奶油、冰块,类名就开始往外冒。

用继承表示每种咖啡搭配导致类越来越多

这写法能跑,但搭配越多,类越多。改个价格,也得来回找。

装饰器模式换了个思路:保留原来的咖啡,需要什么配料,就在外面包一层。

先看关键 UML 图

类图里最重要的是 CoffeeDecorator 手里还拿着一个 Coffee。

装饰器模式关键 UML 类图

Americano 是原始对象。

MilkDecorator 和 SugarDecorator 是装饰器。它们自己也是 Coffee,同时又包着另一个 Coffee。

看着有点绕,先记住:外面那层和里面那层,用的是同一个接口。

先准备一杯普通咖啡

接口只放两个操作:描述和价格。

public interface Coffee {
    String getDescription();
    int getPrice();
}

美式咖啡是最里面那一层。

public class Americano implements Coffee {
    public String getDescription() {
        return "美式咖啡";
    }

    public int getPrice() {
        return 10;
    }
}

到这里还没用设计模式,就是一杯 10 元的普通美式。

装饰器先把咖啡接住

所有配料装饰器都有一个共同点:内部要保存被包装的咖啡。

public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee coffee;

    protected CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

CoffeeDecorator 也实现了 Coffee。所以外面的代码,照样能把它当成咖啡用。

牛奶是在原价上加 2 元

牛奶装饰器先问里面那杯咖啡多少钱,再加 2 元。

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return coffee.getDescription() + " + 牛奶";
    }

    public int getPrice() {
        return coffee.getPrice() + 2;
    }
}

它没有改 Americano,只是从外面包了一层。

美式咖啡外面逐层包上牛奶和糖

糖装饰器也差不多,只是价格加 1 元,描述后面加上“糖”。

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return coffee.getDescription() + " + 糖";
    }

    public int getPrice() {
        return coffee.getPrice() + 1;
    }
}

一层一层包起来

实际使用时,先创建美式,再包牛奶,外面接着包糖。

Coffee coffee = new Americano();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);

System.out.println(coffee.getDescription());
System.out.println("价格:" + coffee.getPrice() + "元");

输出:

美式咖啡 + 牛奶 + 糖
价格:13元

最外层是 SugarDecorator。它先往里问,问到 MilkDecorator,再问到 Americano。

装饰器逐层调用并计算咖啡价格

里面返回 10 元,牛奶加 2 元,糖再加 1 元,算出来是 13 元。

青岛话说,这个顺序捋顺了,装饰器就不难了。

它和继承有啥区别

继承通常在写代码时,就把功能固定在类里面。

装饰器可以在程序运行时自由搭配。只加糖、只加奶,或者两样都加,都不用再写咖啡组合类。

装饰器模式和继承的区别

不过也别把继承一棍子打死。类型关系稳定、功能不需要随意叠加时,普通继承更直白。

包装顺序会不会影响结果

这杯咖啡里,先加奶再加糖,和先加糖再加奶,价格都是 13 元。

但现实项目不一定。比如先压缩再加密,和先加密再压缩,结果可能不同。

所以装饰器可以自由组合,不代表顺序永远无所谓。这一点得看每层具体做了什么。

啥时候适合用装饰器

功能能一层层叠加,又不想总改原来的类,可以考虑装饰器。

什么时候适合使用装饰器模式

Java 的输入输出流就用了类似思路。基础流外面可以继续包装缓冲、数据读取等能力。

这篇先不展开源码,小白先把咖啡这层想明白就行。

如果功能固定,也没几种搭配,写一个普通方法更省事。包装七八层以后,排查问题也容易犯迷糊。

狗哥收个尾

装饰器模式的重点,是 用包装给对象增加功能。

原始咖啡不动,牛奶和糖各管一层。所有对象都实现 Coffee,所以可以像套杯子一样,一层接一层地组合。

下一篇继续聊 外观模式。家里一堆设备开机太麻烦,咱给它们做个“一键开影院”。


评论