掘金 后端 ( ) • 2024-05-14 18:45

装饰器模式(Decorator Pattern)

一种结构型设计模式。它可以动态地给一个对象添加一些额外的功能(这句话就是核心)。就扩展功能来说,它相比生成子类更为灵活。

秒懂案例

前言

假设我们有一个简单的Coffee类,它表示一杯咖啡,并且有一个getCost()方法用于计算咖啡的价格。

现在,如果我们想添加额外的扩展功能(比如加奶咖啡、加糖咖啡等)。

不想修改Coffee类的代码,这种情况我们就可以使用装饰器模式。

1. 首先,定义Coffee接口 及 它的实现类SimpleCoffee

// Coffee 接口  
interface Coffee {  
    double getCost();  
}  
  
// SimpleCoffee 类,实现了 Coffee 接口  
class SimpleCoffee implements Coffee {  
    @Override  
    public double getCost() {  
        return 2.5; // 假设一杯基础咖啡的价格是2.5元  
    }  
}

*说明:这样一杯咖啡的功能就实现了

2. 定义抽象类CoffeeDecorator,实现Coffee接口 并持有一个 Coffee对象的引用

// CoffeeDecorator 抽象类  
abstract class CoffeeDecorator implements Coffee {  
    protected Coffee coffee;  
  
    public CoffeeDecorator(Coffee coffee) {  
        this.coffee = coffee;  
    }  
  
    @Override  
    public double getCost() {  
        return coffee.getCost(); // 默认情况下,返回被装饰咖啡的价格  
    }  
}

*说明:这里就是装饰器的思想了,将同类以构造的方式拿到后进行组合及扩展

3.1. 创建具体的装饰器类,例如:加奶咖啡

// MilkCoffee 类,继承自 CoffeeDecorator  
class MilkCoffee extends CoffeeDecorator {  
    public MilkCoffee(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public double getCost() {  
        return super.getCost() + 0.5; // 在原价格基础上加0.5美元(加奶的费用)  
    }  
  
    public String getDescription() {  
        return "加奶的";  
    }  
}  

*说明:继承抽象装饰器,根据传入的品类结合自身进行扩展。

3.2. 创建具体的装饰器类,例如:加糖咖啡

// SugarCoffee 类,继承自 CoffeeDecorator  
class SugarCoffee extends CoffeeDecorator {  
    public SugarCoffee(Coffee coffee) {  
        super(coffee);  
    }  
  
    @Override  
    public double getCost() {  
        return super.getCost() + 0.3; // 在原价格基础上加0.3美元(加糖的费用)  
    }  
  
    public String getDescription() {  
        return "加糖的";  
    }  
}

4. 组合装饰器 构成各种各样的新品咖啡

public class CoffeeShop {  
    public static void main(String[] args) {  
        // 创建一个基础咖啡对象  (来一杯苦咖啡)
        Coffee coffee = new SimpleCoffee();  
        System.out.println("基础咖啡的价格: $" + coffee.getCost());  
  
        // 使用装饰器添加奶  (牛奶咖啡新品出炉)
        Coffee milkCoffee = new MilkCoffee(coffee);  
        System.out.println(milkCoffee.getDescription() + "咖啡的价格: $" + milkCoffee.getCost());  
  
        // 在加奶的咖啡基础上再加糖  (加糖的牛奶咖啡新品出炉)
        Coffee milkSugarCoffee = new SugarCoffee(milkCoffee);  
        System.out.println(milkSugarCoffee.getDescription() + "并且" + milkSugarCoffee.getDescription() + "的咖啡的价格: $" + milkSugarCoffee.getCost());  
    }  
}

疑问:CoffeeDecorator类的作用

  1. 代码重用和减少冗余:如果所有的装饰器都直接实现Coffee接口,那么它们都需要重复实现一些公共的方法或属性。通过创建一个装饰器抽象类,你可以将公共的方法或属性放在这个抽象类中,从而减少代码的冗余和提高代码的可维护性。
  2. 简化装饰器之间的交互:如果装饰器之间需要相互协作或访问彼此的状态,通过继承一个共同的装饰器抽象类,可以更容易地实现这些交互。抽象类可以提供一些保护成员(protected members)或方法,供子类访问和使用。
  3. 类型检查和安全性:通过继承装饰器抽象类,你可以在编译时进行类型检查,确保所有的装饰器都遵循某种预定的约定或行为。此外,使用抽象类可以防止客户端错误地将不适当的对象传递给装饰器构造函数,因为编译器会要求传入的对象是装饰器抽象类或其子类的实例。
  4. 可扩展性和维护性:随着系统的发展,你可能需要添加新的装饰器或修改现有的装饰器。如果所有的装饰器都直接实现接口,那么当你需要修改或添加新的公共方法时,你需要修改所有的装饰器实现。而如果使用装饰器抽象类,你只需要在抽象类中修改一次,所有的子类都会继承这个修改。
  5. 设计模式一致性:装饰器模式通常与抽象类一起使用,这是因为它符合该模式的核心思想:动态地给一个对象添加一些额外的职责。通过继承一个共同的抽象类,你可以更清晰地表达这种“添加职责”的概念。

感谢阅读,后续我会分享更多的设计模式知识,虽然也有很多博主分享过,但我有自己的方法,我的方法就是:简单通俗易懂,哈哈。