掘金 后端 ( ) • 2024-03-30 10:21

theme: qklhk-chocolate highlight: a11y-dark

装饰器模式(Decorator Pattern) 是一种结构型设计模式,它允许行为在运行时被附加到一个对象上,而不影响其他对象的行为。装饰器模式是通过创建一个包装对象来实现的,该包装对象包含了原始对象,并且可以在执行原始对象的操作之前或之后执行额外的操作。

举个例子,比如我们有一杯普通的咖啡,我们想要让它变得更加特别一些,我们可以先在外面包一层巧克力,然后再在上面撒些彩色砂糖,再放上一片鲜奶油。这些包装就像是装饰器,它们不会改变咖啡本身的味道,但会让它看起来更漂亮、更有吸引力。

在编程中,装饰器模式也是类似的,我们可以在不改变原始对象的情况下,通过添加一些额外的功能来装饰它。这些额外的功能可以是在原有功能之前或之后执行一些操作,或者是给原有功能添加一些新的特性,但最终对象的核心功能仍然是一样的。

主要角色:

  1. 组件(Component): 定义了被装饰对象和装饰器共同实现的接口。
  2. 具体组件(Concrete Component): 实现了组件接口的具体对象,是被装饰的对象。
  3. 装饰器(Decorator): 继承自组件接口,并持有一个组件对象的引用,通过该引用可以调用被装饰对象的方法,并可以在调用前后执行额外的操作。
  4. 具体装饰器(Concrete Decorator): 扩展了装饰器,实现了额外的操作。

示例:

用java代码实现一下上面这个咖啡店的例子,咖啡是组件,可以被装饰的调料如牛奶和糖是具体装饰器。

package com.luke.designpatterns.decoratorPattern;

// 组件接口
interface Coffee {
    double cost();
}

// 具体组件 - 咖啡
class SimpleCoffee implements Coffee {
    @Override
    public double cost() {
        return 5.0;
    }
}

// 装饰器
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }
}

// 具体装饰器 - 牛奶
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public double cost() {
        return super.cost() + 2.0;
    }
}

// 具体装饰器 - 糖
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public double cost() {
        return super.cost() + 1.0;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建简单咖啡
        Coffee coffee = new SimpleCoffee();
        System.out.println("简单咖啡:" + coffee.cost() + "元");

        // 使用装饰器加牛奶
        Coffee milkCoffee = new MilkDecorator(coffee);
        System.out.println("加牛奶的咖啡:" + milkCoffee.cost() + "元");

        // 使用装饰器加牛奶和糖
        Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
        System.out.println("加牛奶和糖的咖啡:" + sugarMilkCoffee.cost() + "元");
    }
}

image.png

在这个例子中,Coffee 是组件接口,SimpleCoffee 是具体组件,CoffeeDecorator 是装饰器。MilkDecoratorSugarDecorator 是具体装饰器,它们通过继承和组合的方式实现对咖啡的装饰。客户端可以选择使用不同的装饰器组合来获取不同口味的咖啡,而不需要改变原始的咖啡对象。这符合开闭原则,即对扩展开放,对修改关闭。

适用场景

装饰器模式适用于以下场景:

  1. 动态地给对象添加额外的功能:当需要在不修改原始对象的情况下,动态地添加功能或行为时,装饰器模式非常有用。例如,在运行时根据需求添加日志记录、权限检查等功能。
  2. 避免创建子类的过多:当需要为类的功能添加多个不同的组合时,使用继承可能导致类爆炸,而装饰器模式允许通过组合的方式动态地添加功能,避免创建过多的子类。
  3. 保持类的简单性:装饰器模式可以保持原始类的简单性,因为它们只需要关注核心功能,而将装饰逻辑分离出来,这样使得类的设计更加清晰。
  4. 动态地移除功能:与添加功能相反,装饰器模式也可以用于动态地移除对象的功能。这在某些情况下可能是有用的,例如根据条件选择性地移除某些功能。
  5. 需要透明地扩展对象的功能:装饰器模式允许透明地扩展对象的功能,即客户端代码不需要知道对象是否被装饰,仍然可以使用原始对象的接口。

总的来说,装饰器模式适用于需要动态地添加或移除对象功能,同时保持对象简单性和透明性的情况。