装饰者模式是一种结构型设计模式,用于动态地给对象添加额外的行为或责任,而无需修改其原始类。这种模式允许你将对象包装在一个装饰者类中,并在运行时动态地添加新的行为,同时保持接口的一致性。下面我将通过一个简单的咖啡店订单系统的例子来说明装饰者模式。
背景介绍
假设你经营一家咖啡店,你的订单系统需要处理各种类型的咖啡饮品。每种咖啡饮品都有一个基本价格,但顾客可以选择在咖啡中添加额外的配料,如牛奶、糖浆或奶泡,每种配料都会额外收费。
实现思路
- 定义一个抽象的咖啡饮品接口 Coffee,其中包含计算价格和获取描述的方法。
- 创建具体的咖啡类 SimpleCoffee,实现 Coffee 接口,表示基本的咖啡饮品。
- 建立装饰者类 CoffeeDecorator,它也实现了 Coffee 接口,包含一个 Coffee 对象的引用,用于动态添加新的行为。
- 创建具体的装饰者类,如 MilkDecorator 和 SugarDecorator,用于给咖啡中添加牛奶和糖等配料。
- 在客户端代码中,通过组合不同的装饰者类,动态地构建不同种类的咖啡饮品。
具体实现
首先,我们有一个抽象的咖啡饮品接口 Coffee:
package blog.装饰者模式;
public interface Coffee {
double cost(); // 计算价格
String getDescription(); // 获取描述
}
然后,我们有一个具体的咖啡类 SimpleCoffee,它实现了 Coffee 接口:
package blog.装饰者模式;
public class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 1.0; // 基本价格
}
@Override
public String getDescription() {
return "Simple coffee"; // 基本描述
}
}
接下来,我们创建装饰者类 CoffeeDecorator,它也实现了 Coffee 接口,并包含一个 Coffee 对象的引用,用于动态添加新的行为:
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee decoratedCoffee) {
this.decoratedCoffee = decoratedCoffee;
}
@Override
public double cost() {
return decoratedCoffee.cost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
现在,我们可以创建具体的装饰者类,比如 MilkDecorator,它用于给咖啡中添加牛奶:
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double cost() {
return super.cost() + 0.5; // 牛奶价格
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk"; // 追加描述
}
}
我们再创建一个具体的装饰者类,SugarDecorator,用于给咖啡加糖
public class SugarDecorator extends CoffeeDecorator{
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double cost() {
return super.cost() + 0.5; // 糖价格
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar"; // 追加描述
}
}
使用装饰者模式,我们可以动态地给咖啡添加各种配料,而不需要修改原始的 Coffee 类。例如:
public class Main {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost: " + simpleCoffee.cost() + "; Description: " + simpleCoffee.getDescription());
Coffee milkCoffee = new MilkDecorator(simpleCoffee);
System.out.println("Cost: " + milkCoffee.cost() + "; Description: " + milkCoffee.getDescription());
Coffee milkAndSugarCoffee = new SugarDecorator(milkCoffee);
System.out.println("Cost: " + milkAndSugarCoffee.cost() + "; Description: " + milkAndSugarCoffee.getDescription());
}
}
输出:
总结
装饰者模式是一种灵活的设计模式,它有许多优点和一些缺点。
优点:
动态性: 装饰者模式允许你在运行时动态地添加或删除对象的功能,而不需要修改其原始类,这使得系统更加灵活。
遵循开闭原则: 装饰者模式通过组合而不是继承来扩展对象的功能,因此遵循开闭原则,即对修改关闭,对扩展开放。
单一职责原则: 装饰者模式使得每个类只需要关注自己的核心功能,装饰者类负责附加功能,从而遵循了单一职责原则。
可复用性: 装饰者模式允许你独立地组合不同的装饰者类,这些装饰者可以被复用于不同的对象,增加了代码的可复用性。
保持对象接口一致性: 装饰者模式通过实现相同的接口来包装不同的对象,因此不会改变对象的外部行为,保持了对象接口的一致性。
缺点:
可能产生过多的小对象: 使用装饰者模式会导致系统中出现大量的小对象,如果过度使用装饰者模式,可能会导致对象数量过多,增加系统的复杂性。
理解和维护难度: 装饰者模式涉及到多层嵌套的装饰者类,可能会增加代码的理解和维护难度,特别是对于复杂的装饰者组合结构。
不适用于所有情况: 装饰者模式适用于需要动态地给对象添加功能的场景,但并不适用于所有情况,有时候使用其他设计模式如策略模式或状态模式可能更合适。
尽管装饰者模式存在一些缺点,但在需要动态地扩展对象功能而又不想修改原始类的情况下,它仍然是一个强大且灵活的设计模式。