Menu
CoddyTech

装饰器模式

本课属于 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

当你需要为对象添加职责,又不想创建大量子类时,装饰器模式就能发挥作用。每个装饰器都是独立的,因此你可以自由组合它们,准确实现所需的行为组合。

challenge icon

挑战

简单

让我们使用装饰器模式构建一个通知系统!你将创建一个基础通知,并通过相互包装装饰器,为它添加不同的发送渠道,包括电子邮件提醒、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 方法
        
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Java 在线编译器