装饰器模式(Decorator Pattern),又称包装器模式(Wrapper Pattern),是一种结构型设计模式。它的主要目的是在不改变原有对象结构的基础上,动态地给该对象增加一些额外的职责(即增加其额外功能)。装饰器模式相比通过继承子类来实现功能的扩展更加灵活,因为它可以在运行时根据需要添加或移除装饰器,从而实现功能的动态组合。
定义与特点
- 定义:装饰器模式在不改变原有对象结构的情况下,通过创建一个包装对象(即装饰器)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能。
- 特点:
- 灵活性:相比继承,装饰器模式提供了更大的灵活性,可以在不修改原有类的情况下增加新的功能。
- 开闭原则:装饰器模式符合开闭原则,即对扩展开放,对修改关闭。可以通过增加新的装饰类来扩展功能,而无需修改原有类。
- 组合性:装饰器可以嵌套使用,通过不同的装饰器组合可以实现多种功能的叠加。
角色与结构
装饰器模式主要包含以下几个角色:
- 抽象构件(Component):定义一个对象接口,可以给这些对象动态地添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- 具体构件(Concrete Component):定义了一个具体的对象,也可以给这个对象添加一些额外的职责。
- 抽象装饰器(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象构件一致的接口。
- 具体装饰器(Concrete Decorator):负责给构件添加新的职责。
优点与缺点
优点
- 灵活性:能够动态地给对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
- 开闭原则:通过扩展装饰类来实现功能的扩展,符合开闭原则。
- 减少子类数量:避免了使用继承带来的子类膨胀问题。
缺点
- 多层装饰复杂:如果装饰器过多,会导致系统设计中类的数量过多,增加系统的复杂度。
- 排查问题困难:在多层装饰的情况下,排查问题可能需要层层深入,增加了调试的难度。
应用场景
装饰器模式适用于以下场景:
- 当需要给一个对象动态地添加一些额外的职责时。
- 当不能采用继承的方式对系统进行扩展时(例如,系统类定义被隐藏,或类为final类等)。
- 当一个类存在多个扩展点,或者一个类的扩展点经常变化时。
示例
以煎饼果子为例,可以定义一个抽象构件(煎饼果子),一个具体构件(原味煎饼果子),然后通过不同的装饰器(如加鸡蛋、加火腿等)来实现不同口味的煎饼果子。这样,在不改变原味煎饼果子类结构的情况下,就可以通过添加不同的装饰器来得到不同口味的煎饼果子。
在Java中,装饰器模式(Decorator Pattern)通过创建一个包装对象(即装饰器)来包裹真实对象,并在不改变其结构的情况下动态地给该对象添加一些额外的职责(即功能)。以下是一个简单的Java示例,展示如何使用装饰器模式来增强一个Coffee
对象的功能。
首先,我们定义一个Coffee
接口,它代表咖啡的基类:
public interface Coffee {
double getCost();
String getDescription();
}
然后,我们创建一个实现了Coffee
接口的具体类SimpleCoffee
,它代表简单的咖啡:
public class SimpleCoffee implements Coffee {
@Override
public double getCost() {
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 getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
现在,我们可以创建具体的装饰器类了。例如,我们可以创建一个Milk
装饰器,用于在咖啡中添加牛奶:
public class Milk extends CoffeeDecorator {
public Milk(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5; // 假设牛奶的额外成本是0.5
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
}
类似地,我们可以创建另一个装饰器Whip
,用于在咖啡上添加奶泡:
public class Whip extends CoffeeDecorator {
public Whip(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public double getCost() {
return super.getCost() + 0.3; // 假设奶泡的额外成本是0.3
}
@Override
public String getDescription() {
return super.getDescription() + ", Whip";
}
}
最后,我们可以使用这些类来创建并装饰一个咖啡对象:
public class CoffeeOrder {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
coffee = new Milk(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
coffee = new Whip(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
}
}
输出将是:
Simple Coffee $1.0
Simple Coffee, Milk $1.5
Simple Coffee, Milk, Whip $1.8
这个示例展示了如何使用装饰器模式来动态地给SimpleCoffee
对象添加Milk
和Whip
功能,并计算了每个阶段的成本和描述。通过组合不同的装饰器,我们可以创建出多种口味的咖啡。