装饰器模式

2025-06-07

装饰器模式

什么是装饰器模式?

装饰器模式是一种结构化设计模式,可以动态地为对象附加额外的行为。装饰器通过组合而非子类化(继承)提供了灵活的扩展能力。

何时使用它?

当您有许多可能的组合来构造对象时,请使用装饰器模式。

问题

假设我们正在为一家冰淇淋店开发一个系统。这家店有各种冰淇淋和配料。系统需要显示冰淇淋的描述(包括配料)和价格。

我们的第一个版本系统是这样的:

图片描述

这看起来不错,但如果我们可以添加配料呢?

我们的第二个版本:

图片描述

问题揭晓了!注意,即使它们还不够,因为我们还需要ChocolateIceCreamWithChocolateChipsAndMapleNuts等等。

解决方案

图片描述

装饰器模式又称为包装器模式,因为它通过为对象包装额外的功能来实现。被包装的对象(冰淇淋)被称为组件,而包装器对象(冰淇淋顶部)被称为装饰器

  1. IceCream 类
    组件和装饰器具有通用的接口IceCream类(稍后您将看到原因),它们都声明为IceCream对象。

  2. 具体的冰淇淋类别
    每种具体的冰淇淋都会覆盖成本方法,因为每种冰淇淋的价格都不同。

  3. Topping 类
    Topping 类为具体的配料提供接口并保存对IceCream对象的引用。

  4. 具体的配料类
    如果系统需要另一种配料,比如焦糖源,您需要做的只是创建CaramelSource扩展Topping类的类。

结构

图片描述

装饰器类使用组合和继承,理解它们的意图至关重要。
在装饰器模式中,我们对组件和装饰器使用相同的类型。装饰器组合组件对象以获取行为,即获取组件对象中定义的字段或方法。而装饰器继承(扩展)组件,因此装饰器对象可以声明为组件对象。

装饰器模式遵循开放-封闭原则,即对扩展开放,对修改关闭。添加组件或装饰器非常简单。例如,如果要添加另一个具体的装饰器,只需创建一个代表它的类并扩展 Decorator 类即可。

Java 中的实现

// Component class
public abstract class IceCream {

    public String description = "Unknown ice cream";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
Enter fullscreen mode Exit fullscreen mode
// Concrete component class
public class ChocolateIceCream extends IceCream {

    public ChocolateIceCream() {
        description = "ChocolateIceCream";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}
Enter fullscreen mode Exit fullscreen mode
// Base decorator class
public abstract class Topping extends IceCream {

    public IceCream iceCream;

    // All subclasses (concrete decorator classes) need to implement getDescription method,
    // by declaring this method as abstract, we enforce all subclasses to implement this method
    public abstract String getDescription();
}
Enter fullscreen mode Exit fullscreen mode
// Concrete decorator class
public class MapleNuts extends Topping {

    public MapleNuts(IceCream iceCream) {
        this.iceCream = iceCream;
    }

    @Override
    public String getDescription() {
        return iceCream.getDescription() + ", MapleNuts";
    }

    @Override
    public double cost() {
        return iceCream.cost() + .30;
    }
}
Enter fullscreen mode Exit fullscreen mode
// Concrete decorator class
public class PeanutButterShell extends Topping {

    public PeanutButterShell(IceCream iceCream) {
        this.iceCream = iceCream;
    }

    @Override
    public String getDescription() {
        return iceCream.getDescription() + ", PeanutButterShell";
    }

    @Override
    public double cost() {
        return iceCream.cost() + .30;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class Client {

    public static void main(String[] args) {
        IceCream iceCream = new ChocolateIceCream();
        System.out.println(iceCream.getDescription() + ", $" + iceCream.cost());

        iceCream = new MapleNuts(iceCream);
        System.out.println(iceCream.getDescription() + ", $" + iceCream.cost());

        iceCream = new PeanutButterShell(iceCream);
        System.out.println(iceCream.getDescription() + ", $" + iceCream.cost());
    }
}
Enter fullscreen mode Exit fullscreen mode

输出:

ChocolateIceCream, $1.99
ChocolateIceCream, MapleNuts, $2.29
ChocolateIceCream, MapleNuts, PeanutButterShell, $2.59
Enter fullscreen mode Exit fullscreen mode

陷阱

  • 通常会导致大量的类被添加到系统中,其中每个类都提供小的行为。
  • 如果要让每个装饰器都了解内部装饰器,则需要进行一些额外的工作。在上面的例子中,如果您想打印ChocolateIceCream, MapleNuts * 2而不是打印ChocolateIceCream, MapleNuts, MapleNuts,则每个装饰器都需要知道到目前为止添加了哪些配料(例如,可以通过 ArrayList 来实现)。

您可以在这里查看所有设计模式的实现。GitHub
仓库


附言:
我是科技博客的新手,如果你有什么改进建议,或者有什么困惑的地方,请留言!
感谢阅读 :)

文章来源:https://dev.to/sota_333ad4b72095606ab40c/decorator-pattern-3m7k
PREV
通过使用更多的终端和更少的鼠标来提高您的工作效率(🚀)。
NEXT
如何从 GitHub 上的提交历史记录中删除敏感文件