装饰器模式
本课属于 Coddy Java 学习旅程的“面向对象编程”单元。第 72 课,共 87 课。
装饰器模式是一种结构型设计模式,它通过用称为装饰器的特殊对象包装对象,为对象添加新的行为。与在编译时添加行为的继承不同,装饰器可以在运行时动态添加功能,而无需修改原始类。
想象一家咖啡店,你先点一杯基础咖啡,然后可以添加牛奶、糖或鲜奶油等配料。每添加一种配料,都会包装原来的咖啡,并增加相应的费用和描述。这个模式使用一个通用接口,因此装饰后的对象可以像原始对象一样处理:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}装饰器会持有对被包装对象的引用,并在添加自身行为的同时将调用委托给它。你可以叠加多个装饰器来组合不同功能:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // 普通咖啡、牛奶、糖
System.out.println(order.getCost()); // 2.75当你需要为对象添加职责,又不想创建大量子类时,装饰器模式就能发挥作用。每个装饰器都是独立的,因此你可以自由组合它们,准确实现所需的行为组合。
挑战
简单让我们使用装饰器模式构建一个通知系统!你将创建一个基础通知,并通过相互包装装饰器,为它添加不同的发送渠道,包括电子邮件提醒、SMS 提醒和推送通知。这模拟了现实中的系统:一条消息可能需要同时通过多个渠道发送。
你需要将代码分布在 4 个文件中:
Notifier.java:定义Notifier接口,作为装饰器模式的组件接口。它应声明一个用于发送通知的方法send(String message)。BasicNotifier.java:创建实现Notifier的具体组件类。它代表最简单的通知形式:应用内提醒。调用send时,它应打印In-App: [message]。NotifierDecorators.java:在这里构建装饰器层次结构。首先创建抽象类NotifierDecorator,让它实现Notifier,并持有一个被包装的Notifier对象的引用(通过它的构造函数传入)。然后创建 3 个继承
NotifierDecorator的具体装饰器:EmailDecorator:调用send时,它会先委托给被包装的通知器,然后打印Email: [message]。SMSDecorator:调用send时,它会先委托给被包装的通知器,然后打印SMS: [message]。PushDecorator:调用send时,它会先委托给被包装的通知器,然后打印Push: [message]。Main.java:整合你的装饰器系统!你会收到两个输入:通知消息(String)和要添加的、以逗号分隔的渠道列表(String),例如"email,sms"或"push,email,sms"。从一个
BasicNotifier开始。然后,根据渠道输入,按它们出现的顺序用相应的装饰器包装它。有效的渠道名称有email、sms和push。最后,在完全装饰后的通知器上调用
send,并传入你的消息。
你会依次收到两个输入:要发送的消息(String)和要启用的渠道(以逗号分隔的 String)。
例如,输入 Server is down! 和 email,push 时,输出应为:
In-App: Server is down!
Email: Server is down!
Push: Server is down!注意,每个装饰器都会添加自己的行为,同时保留原始通知。包装顺序决定输出顺序:最内层的组件(BasicNotifier)先执行,然后每个装饰器再添加自己的层。你可以自由组合任意渠道,无需为每种组合单独创建类!
亲手试试
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取要发送的消息
String message = scanner.nextLine();
// 读取以逗号分隔的渠道列表(例如 "email,sms" 或 "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: 从 BasicNotifier 开始
// TODO: 按逗号拆分渠道输入
// TODO: 遍历每个渠道,用对应的装饰器包装通知器
// 有效的渠道有:"email"、"sms"、"push"
// TODO: 用消息调用完成装饰的通知器的 send 方法
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Java 在线编译器