行为型模式(上)——策略、模板方法与观察者

摘要

行为型设计模式(Behavioral Patterns)关注对象之间的通信与职责分配——不是对象长什么样,而是对象如何协作、如何传递消息、如何在运行时灵活地改变行为。本文深入剖析三个最核心的行为型模式。策略模式(Strategy) 解决”算法族的封装与互换”问题:当同一个操作有多种实现方式时,将每种方式封装为独立的策略对象,让调用方在运行时选择——这是消灭大量 if-else / switch 的标准手段,也是 OCP 最直接的落地;深入分析策略模式在 Java 8 函数式接口中的演进,以及它与工厂方法如何配合实现优雅的策略注册机制。模板方法模式(Template Method) 解决”算法骨架固定、步骤可定制”的问题:父类定义不变的执行流程,子类只实现变化的步骤——这是 OCP 在继承体系中的体现,Spring JdbcTemplateAbstractQueuedSynchronizer 都是工业级应用;剖析钩子方法(Hook Method)的设计哲学与反例。观察者模式(Observer) 解决”对象间一对多的依赖通知”问题:当一个对象状态变化时,所有依赖它的对象都应自动收到通知——这是发布-订阅模式的基础,是事件驱动架构的核心;分析 Java 内置的 Observer/Observable、Guava EventBus、Spring ApplicationEvent 机制的实现差异与选择依据。


第 1 章 策略模式(Strategy)

1.1 动机:算法变化引发的 if-else 泥潭

策略模式(Strategy Pattern)的动机来自一个极为普遍的代码坏味道:当同一个功能有多种实现方式,且需要在运行时选择时,最直觉的做法是写一大堆 if-elseswitch

// 反例:促销折扣计算,随着业务发展不断膨胀的 if-else
public class DiscountCalculator {
    public double calculate(Order order, String promotionType) {
        double price = order.getOriginalPrice();
        
        if ("NORMAL".equals(promotionType)) {
            // 普通用户:无折扣
            return price;
        } else if ("VIP".equals(promotionType)) {
            // VIP 用户:九折
            return price * 0.9;
        } else if ("SUPER_VIP".equals(promotionType)) {
            // 超级 VIP:八折
            return price * 0.8;
        } else if ("COUPON".equals(promotionType)) {
            // 优惠券:减固定金额
            return Math.max(0, price - order.getCouponAmount());
        } else if ("DOUBLE_ELEVEN".equals(promotionType)) {
            // 双十一满减:满 200 减 30
            return price - (long)(price / 200) * 30;
        } else if ("BIRTHDAY".equals(promotionType)) {
            // 生日特惠:七五折
            return price * 0.75;
        } else if ("FLASH_SALE".equals(promotionType)) {
            // 限时秒杀:五折
            return price * 0.5;
        }
        // 未来还会加更多...
        throw new IllegalArgumentException("Unknown promotion: " + promotionType);
    }
}

这段代码的问题已经在 OCP 章节分析过——每次新增促销类型都需要修改这个方法,且随着促销规则越来越复杂,整个方法会变得难以理解和测试。

策略模式的核心思想是:将每种算法(促销规则)封装到独立的类中,让这些类实现同一个接口,调用方持有接口引用,在运行时注入不同的实现

1.2 策略模式的结构

// 策略接口:所有促销规则的公共契约
@FunctionalInterface  // 只有一个方法,可以作为函数式接口
public interface DiscountStrategy {
    double apply(Order order);
}
 
// 具体策略一:VIP 折扣
public class VipDiscountStrategy implements DiscountStrategy {
    private final double discountRate;
    
    public VipDiscountStrategy(double discountRate) {
        this.discountRate = discountRate;
    }
    
    @Override
    public double apply(Order order) {
        return order.getOriginalPrice() * discountRate;
    }
}
 
// 具体策略二:优惠券减免
public class CouponDiscountStrategy implements DiscountStrategy {
    @Override
    public double apply(Order order) {
        return Math.max(0, order.getOriginalPrice() - order.getCouponAmount());
    }
}
 
// 具体策略三:满减(双十一)
public class FullReductionStrategy implements DiscountStrategy {
    private final double threshold;   // 满减门槛(如 200)
    private final double reduction;   // 减免金额(如 30)
    
    public FullReductionStrategy(double threshold, double reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }
    
    @Override
    public double apply(Order order) {
        double price = order.getOriginalPrice();
        return price - (long)(price / threshold) * reduction;
    }
}
 
// 上下文(Context):持有策略引用,委托给策略执行
public class DiscountCalculator {
    private DiscountStrategy strategy;
    
    public DiscountCalculator(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    // 允许运行时切换策略
    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public double calculate(Order order) {
        return strategy.apply(order);  // 委托给策略,自己不包含任何 if-else
    }
}

1.3 策略的注册与查找:消灭调用方的 if-else

策略模式解决了策略内部的 if-else,但调用方仍然需要知道”什么条件用什么策略”。如果这个选择逻辑还是用 if-else 来做,问题只是从一处转移到了另一处。

优雅的解法:策略注册表(Strategy Registry),用 Map<String, DiscountStrategy> 将条件直接映射到策略对象,彻底消灭 if-else

// 策略注册表:将策略类型与策略实现绑定
@Component
public class DiscountStrategyRegistry {
    private final Map<String, DiscountStrategy> strategies = new HashMap<>();
    
    // Spring 会自动注入所有 DiscountStrategy 实现类(通过 @Qualifier 或 Bean 名称)
    // 更简单的做法:在构造器中手动注册
    public DiscountStrategyRegistry() {
        // 注册所有策略
        strategies.put("VIP",            new VipDiscountStrategy(0.9));
        strategies.put("SUPER_VIP",      new VipDiscountStrategy(0.8));
        strategies.put("BIRTHDAY",       new VipDiscountStrategy(0.75));
        strategies.put("FLASH_SALE",     new VipDiscountStrategy(0.5));
        strategies.put("COUPON",         new CouponDiscountStrategy());
        strategies.put("DOUBLE_ELEVEN",  new FullReductionStrategy(200, 30));
        strategies.put("NORMAL",         order -> order.getOriginalPrice()); // Lambda!
    }
    
    public DiscountStrategy getStrategy(String promotionType) {
        DiscountStrategy strategy = strategies.get(promotionType);
        if (strategy == null) {
            throw new IllegalArgumentException("Unknown promotion type: " + promotionType);
        }
        return strategy;
    }
}
 
// 调用方:完全没有 if-else
@Service
public class OrderService {
    @Autowired private DiscountStrategyRegistry registry;
    
    public double calculateFinalPrice(Order order) {
        DiscountStrategy strategy = registry.getStrategy(order.getPromotionType());
        return strategy.apply(order);
    }
}

现在,新增一种促销类型只需要:① 创建新的 DiscountStrategy 实现(或一个 Lambda);② 在 DiscountStrategyRegistryput 一行注册。调用方代码(OrderService零改动,严格遵守 OCP。

1.4 Java 8 之后:Lambda 就是策略

Java 8 引入函数式接口后,策略模式的实现变得极为轻量。当策略接口只有一个方法(@FunctionalInterface)时,策略对象可以直接用 Lambda 表达式或方法引用表示,无需创建专门的实现类:

// 在注册表中,简单的策略用 Lambda 表达式内联定义
strategies.put("NORMAL",       order -> order.getOriginalPrice());
strategies.put("VIP",          order -> order.getOriginalPrice() * 0.9);
strategies.put("BIRTHDAY",     order -> order.getOriginalPrice() * 0.75);
 
// 复杂策略仍然可以是独立类(便于测试和复用)
strategies.put("DOUBLE_ELEVEN", new FullReductionStrategy(200, 30));

ComparatorRunnablePredicateFunction 这些 JDK 内置函数式接口,本质上都是策略接口——它们都封装了一种”可变的算法或行为”,允许调用方在运行时注入不同的实现。Collections.sort(list, comparator) 就是经典的策略模式应用:comparator 就是”比较策略”。

1.5 策略模式 vs 简单工厂:职责不同

策略模式和工厂方法都与”选择不同实现”有关,容易混淆,但两者职责截然不同:

  • 工厂方法关注”如何创建对象”——客户端不知道具体创建哪个类,由工厂决定;
  • 策略模式关注”如何执行算法”——客户端知道自己需要什么策略,但策略内部是黑箱。

在实践中,它们经常组合使用:注册表(Map)扮演工厂角色,负责策略对象的查找和创建;策略接口定义算法行为,供调用方使用。


第 2 章 模板方法模式(Template Method)

2.1 动机:流程固定、步骤可变

模板方法模式(Template Method Pattern)解决的问题是:一个算法的整体执行流程是固定的,但其中某些步骤的具体实现因场景而异

最典型的生活类比是”制作饮料”的过程:

  1. 烧水(固定步骤,不变)
  2. 把原料放入杯中(变化:茶叶 vs 咖啡粉)
  3. 用热水冲泡(固定步骤,不变)
  4. 根据喜好加调料(变化:茶加柠檬 vs 咖啡加糖和牛奶)

如果不用模板方法,“制作茶”和”制作咖啡”的代码会包含大量重复:

// 反例:重复的骨架代码
public class TeaMaker {
    public void makeTea() {
        boilWater();                    // 重复
        steepTeaLeaves();               // 茶特有
        pourInCup();                    // 重复
        addLemon();                     // 可选
    }
}
 
public class CoffeeMaker {
    public void makeCoffee() {
        boilWater();                    // 重复(与上面一模一样)
        brewCoffeeGrinds();             // 咖啡特有
        pourInCup();                    // 重复(与上面一模一样)
        addSugarAndMilk();              // 可选
    }
}

如果”冲泡流程”需要加一步(如”先温杯”),需要修改所有具体类,这正是代码重复带来的维护问题。

2.2 模板方法的结构

模板方法将不变的流程骨架定义在父类中,将变化的步骤定义为 protected abstract 方法(或提供默认实现的 protected 方法),由子类覆写:

// 抽象父类:定义算法骨架
public abstract class BeverageMaker {
    
    // 模板方法(final:禁止子类覆写,保护骨架不被破坏)
    public final void prepareBeverage() {
        boilWater();          // 固定步骤:烧水
        brew();               // 抽象步骤:冲泡(子类实现)
        pourInCup();          // 固定步骤:倒入杯中
        if (customerWantsCondiments()) {   // 钩子方法:是否加调料
            addCondiments();  // 抽象步骤:加调料(子类实现)
        }
    }
    
    // 固定步骤(父类提供实现,子类不需要也不能覆写)
    private void boilWater() {
        System.out.println("Boiling water...");
    }
    
    private void pourInCup() {
        System.out.println("Pouring into cup...");
    }
    
    // 抽象步骤:强制子类提供实现
    protected abstract void brew();
    protected abstract void addCondiments();
    
    // 钩子方法(Hook):提供默认行为,子类可选择覆写
    // 默认加调料,子类可覆写返回 false 表示不加
    protected boolean customerWantsCondiments() {
        return true;
    }
}
 
// 具体子类一:泡茶
public class TeaMaker extends BeverageMaker {
    @Override
    protected void brew() {
        System.out.println("Steeping the tea leaves for 3 minutes...");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon...");
    }
    
    // 不覆写 customerWantsCondiments(),默认加调料
}
 
// 具体子类二:冲咖啡(不喝加料咖啡的纯粹主义者版本)
public class BlackCoffeeMaker extends BeverageMaker {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter...");
    }
    
    @Override
    protected void addCondiments() {
        // 空实现,但实际上 customerWantsCondiments() 返回 false 就不会调用
    }
    
    @Override
    protected boolean customerWantsCondiments() {
        return false;  // 黑咖啡不加任何东西
    }
}

2.3 钩子方法(Hook Method)的设计哲学

钩子方法是模板方法模式中一个微妙而重要的设计点,值得单独深入分析。

什么是钩子方法:钩子方法是父类中提供了默认实现(通常是空实现或返回布尔值)的 protected 方法,子类可以(但不必须)覆写它来改变模板方法的行为。

与抽象方法(abstract,子类必须实现)不同,钩子方法是可选的扩展点

钩子方法的两种形式

// 形式一:返回布尔值的条件钩子(控制流程分支)
protected boolean customerWantsCondiments() { return true; }
 
// 形式二:空实现的事件钩子(在流程的关键节点插入额外逻辑)
protected void afterBrew() { }  // 默认不做任何事,子类可覆写做额外处理

为什么需要钩子方法:如果把所有步骤都定义为 abstract,每个子类都必须实现所有步骤,但某些子类可能对某些步骤没有任何有意义的实现——它们会被迫写空方法或 throw new UnsupportedOperationException(),这是 ISP 的违反。钩子方法通过提供默认(通常是空)实现,让子类只覆写真正有差异的部分。

2.4 工业级应用:Spring JdbcTemplate

Spring 的 JdbcTemplate 是模板方法模式最著名的工业级应用。其核心思路是:JDBC 操作的固定步骤(获取连接、创建语句、处理异常、关闭资源)由 JdbcTemplate 统一处理,变化的步骤(SQL 语句、参数绑定、结果集处理)由调用方以回调形式提供。

// JdbcTemplate 的核心模板方法(简化版)
public <T> T execute(StatementCallback<T> action) {
    Connection con = DataSourceUtils.getConnection(getDataSource());  // 固定:获取连接
    try {
        Statement stmt = con.createStatement();   // 固定:创建 Statement
        applyStatementSettings(stmt);             // 固定:设置超时等参数
        T result = action.doInStatement(stmt);    // 变化:调用方提供的具体 SQL 逻辑
        handleWarnings(stmt);                     // 固定:处理警告
        return result;
    } catch (SQLException ex) {
        throw translateException("StatementCallback", getSql(action), ex);  // 固定:异常转换
    } finally {
        JdbcUtils.closeStatement(stmt);           // 固定:关闭资源
        DataSourceUtils.releaseConnection(con, getDataSource()); // 固定:释放连接
    }
}

调用方只需提供”变化的部分”——SQL 逻辑:

// 调用方只关注 SQL,不需要关心连接管理和异常处理
List<User> users = jdbcTemplate.query(
    "SELECT * FROM users WHERE status = ?",
    new Object[]{1},
    (rs, rowNum) -> {         // 这个 Lambda 就是"结果集处理"的策略/钩子
        return User.builder()
            .id(rs.getLong("id"))
            .name(rs.getString("name"))
            .build();
    }
);

JdbcTemplate 同时体现了模板方法模式(固定的 JDBC 流程骨架)和策略模式(可替换的 SQL 逻辑和结果映射逻辑)。

2.5 模板方法的边界与反例

模板方法 vs 策略模式的选择

维度模板方法(继承)策略模式(组合)
扩展机制继承 + 覆写实现接口 + 注入
骨架控制父类控制(final),子类无法改变调用方自由组合策略
耦合度子类与父类强耦合策略与上下文松耦合
单元测试需要通过子类测试策略可独立单元测试
运行时切换不支持(继承在编译期固定)支持(运行时注入不同策略)
适用场景变体少、骨架稳定、框架扩展点变体多、频繁切换、需独立测试

现代 Java 开发(尤其是 Spring 生态)更倾向于策略模式(组合优于继承),模板方法主要在框架层(JdbcTemplateAbstractQueuedSynchronizerAbstractList)中发挥作用。


第 3 章 观察者模式(Observer)

3.1 动机:对象间的耦合通知问题

观察者模式(Observer Pattern)解决的是一个非常普遍的问题:当一个对象(被观察者/主题)的状态发生变化时,依赖它的多个对象(观察者)需要自动得到通知并做出响应

最直觉但最错误的做法是:在被观察者的状态变化代码里,直接调用每个关心方的方法:

// 反例:订单状态变化时,直接调用所有依赖方
public class Order {
    private OrderStatus status;
    
    @Autowired private EmailService emailService;
    @Autowired private InventoryService inventoryService;
    @Autowired private PointsService pointsService;
    @Autowired private LogisticsService logisticsService;
    @Autowired private StatisticsService statisticsService;
    
    public void complete() {
        this.status = OrderStatus.COMPLETED;
        
        // Order 直接调用所有关心"订单完成"事件的模块
        emailService.sendOrderCompletionEmail(this);
        inventoryService.confirmDeduction(this);
        pointsService.addCompletionPoints(this);
        logisticsService.notifyShipment(this);
        statisticsService.recordCompletedOrder(this);
        // 未来还会加更多...
    }
}

这段代码的 Order 类违反了 SRP:它不仅负责订单业务逻辑,还承担了对多个外部系统的通知职责。更严重的是:

  • 高耦合Order 直接依赖 5 个 Service,任何一个 Service 变化(如 EmailService 接口改变)都可能影响 Order
  • 扩展困难:新增一个”订单完成后推送到 BI 系统”的需求,必须修改 Order 类;
  • 无法解耦发布与订阅时序:所有通知都是同步的,如果某个通知耗时(如发送邮件),会拖慢订单完成的响应速度。

3.2 观察者模式的结构

观察者模式将”通知谁”的决策权从被观察者中剥离出去,由外部注册机制决定:

// 观察者接口(Observer/Listener)
public interface OrderEventListener {
    void onOrderCompleted(Order order);
}
 
// 被观察者(Subject/Publisher):维护观察者列表,状态变化时遍历通知
public class Order {
    private OrderStatus status;
    private final List<OrderEventListener> listeners = new ArrayList<>();
    
    // 注册观察者
    public void addListener(OrderEventListener listener) {
        listeners.add(listener);
    }
    
    // 移除观察者
    public void removeListener(OrderEventListener listener) {
        listeners.remove(listener);
    }
    
    // 业务方法:状态变化后通知所有观察者
    public void complete() {
        this.status = OrderStatus.COMPLETED;
        // Order 不再知道具体有哪些观察者,只负责通知
        notifyListeners();
    }
    
    private void notifyListeners() {
        for (OrderEventListener listener : new ArrayList<>(listeners)) {
            listener.onOrderCompleted(this);
        }
    }
}
 
// 具体观察者一:邮件通知
@Component
public class EmailOrderListener implements OrderEventListener {
    @Override
    public void onOrderCompleted(Order order) {
        emailService.sendOrderCompletionEmail(order.getUserId(), order.getId());
    }
}
 
// 具体观察者二:积分服务
@Component
public class PointsOrderListener implements OrderEventListener {
    @Override
    public void onOrderCompleted(Order order) {
        pointsService.addPoints(order.getUserId(), order.getAmount());
    }
}

现在,新增”订单完成后推送到 BI 系统”只需新增一个 BiSystemOrderListener,注册到 Order 中——Order 类本身不需要任何修改。

3.3 从手工实现到事件驱动框架

上面的手工实现虽然体现了观察者模式,但有局限:

  • 每个”主题”(OrderUser 等)都要维护自己的观察者列表,代码重复;
  • 所有通知都是同步调用,无法做到异步;
  • 观察者与主题之间仍然有直接依赖(需要显式注册)。

现代框架提供了更完善的事件机制。

Guava EventBus

Guava 的 EventBus 是一个进程内的发布-订阅总线,通过注解 @Subscribe 声明事件处理方法,彻底解耦发布者和订阅者:

// 事件类(普通 POJO)
public class OrderCompletedEvent {
    private final Long orderId;
    private final Long userId;
    private final BigDecimal amount;
    // 构造器 + getter ...
}
 
// 订阅者:通过 @Subscribe 声明感兴趣的事件类型
public class EmailNotificationSubscriber {
    @Subscribe  // Guava EventBus 注解
    public void onOrderCompleted(OrderCompletedEvent event) {
        emailService.send(event.getUserId(), "Order " + event.getOrderId() + " completed");
    }
}
 
// 发布者:只依赖 EventBus,不知道有哪些订阅者
@Service
public class OrderService {
    private final EventBus eventBus;
    
    public void completeOrder(Long orderId) {
        Order order = orderRepository.findById(orderId);
        order.complete();
        orderRepository.save(order);
        
        // 发布事件,不关心谁来处理
        eventBus.post(new OrderCompletedEvent(order.getId(), order.getUserId(), order.getAmount()));
    }
}
 
// 应用启动时注册订阅者
eventBus.register(new EmailNotificationSubscriber());
eventBus.register(new PointsSubscriber());

Spring ApplicationEvent

Spring 框架内置了完善的事件机制,基于 ApplicationEvent + ApplicationListener 接口(或 @EventListener 注解):

// 自定义事件(继承 ApplicationEvent)
public class OrderCompletedEvent extends ApplicationEvent {
    private final Order order;
    
    public OrderCompletedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    
    public Order getOrder() { return order; }
}
 
// 发布事件:通过 ApplicationEventPublisher 发布
@Service
public class OrderService {
    @Autowired private ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public void completeOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.complete();
        orderRepository.save(order);
        
        // 发布 Spring 事件(在事务提交后)
        eventPublisher.publishEvent(new OrderCompletedEvent(this, order));
    }
}
 
// 监听事件(@EventListener 注解)
@Component
public class EmailNotificationListener {
    
    @EventListener
    @Async  // 加上 @Async 变成异步处理,不阻塞主流程
    public void handleOrderCompleted(OrderCompletedEvent event) {
        Order order = event.getOrder();
        emailService.sendOrderCompletionEmail(order.getUserId());
    }
}
 
// 或者用 @TransactionalEventListener:确保在事务提交后才处理事件
@Component
public class InventoryListener {
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCompleted(OrderCompletedEvent event) {
        // 只有订单事务成功提交后,才真正确认库存扣减
        inventoryService.confirmDeduction(event.getOrder());
    }
}

@TransactionalEventListener 是 Spring 观察者机制的一个重要特性:它保证只有在发布事件的事务成功提交后,事件处理才会被触发。这解决了一个常见的分布式一致性问题——如果订单入库失败(事务回滚),不应该发送”订单完成”邮件。

3.4 观察者模式的三个层次对比

实现方式耦合程度异步支持适用场景
手工实现(维护 List)观察者与主题轻耦合不支持(需手动)简单场景、教学
Guava EventBus完全解耦(通过注解)AsyncEventBus 支持进程内事件,无 Spring 依赖
Spring ApplicationEvent完全解耦@Async 支持,@TransactionalEventListener 事务感知Spring 项目首选
消息队列(Kafka/RabbitMQ)完全解耦(跨进程)天然异步微服务间事件,跨系统通知

观察者模式的内存泄漏风险

手工实现的观察者模式有一个常见的内存泄漏陷阱:如果观察者被注册后没有被移除(removeListener()),而观察者本身持有大量资源(或是一个短生命周期的对象),那么这个观察者对象会被被观察者的 listeners 列表持有,永远无法被 GC 回收。使用 Spring 的事件机制可以避免这个问题,因为 Spring 容器管理监听器 Bean 的生命周期。


总结

本篇深入剖析了三个最核心的行为型模式:

  • 策略模式将可变的算法封装为独立的策略类,让调用方在运行时注入。消灭 if-else 的关键不只是定义策略接口,还需要配合”策略注册表”(Map<String, Strategy>)来消灭策略选择本身的 if-else。Java 8 之后,函数式接口让简单策略可以直接用 Lambda 表示,无需创建专门的类。策略模式与工厂方法经常组合使用——注册表负责策略的创建和查找,策略接口负责算法执行;

  • 模板方法模式通过继承将不变的算法骨架固化在父类,变化的步骤下放到子类。钩子方法(protectedabstract 方法)提供可选的扩展点,让子类只需覆写有差异的部分。Spring JdbcTemplate 是最权威的工业级实践。在现代设计中,策略模式(组合)比模板方法(继承)更受推荐,但在框架扩展点设计(如 Spring 的各种 Abstract* 类)中,模板方法仍是最优选择;

  • 观察者模式通过注册机制将发布者与订阅者解耦,让状态变化的通知可以任意扩展而不影响被观察者。手工实现适合简单场景;Spring ApplicationEvent + @EventListener 是 Spring 项目的首选,配合 @Async 实现异步通知,配合 @TransactionalEventListener 保证事务一致性——这是生产环境中处理”订单完成后做很多事”的标准模式。

下一篇继续行为型模式中间部分:责任链(Netty ChannelPipeline 的核心)、命令模式(操作对象化,支持撤销/重做)、迭代器(统一遍历接口的本质):07 行为型模式(中)——责任链、命令与迭代器


参考资料

  • GoF,《Design Patterns: Elements of Reusable Object-Oriented Software》, 1994
  • Spring Framework 文档:Application Events and Listeners
  • Guava 文档:EventBus
  • Martin Fowler,《Refactoring》, Replace Conditional with Polymorphism(策略模式的重构出发点)

思考题

  1. 组合模式将对象组织为树形结构。文件系统(目录包含文件和子目录)是经典案例。在组合模式中,叶节点和组合节点实现相同的接口——但叶节点不支持 addChild() 等操作。对叶节点调用 addChild() 应该抛异常还是静默忽略?‘透明性’和’安全性’两种设计取舍在实际项目中如何选择?
  2. 享元模式(Flyweight)共享细粒度对象以节省内存。Java 的 Integer.valueOf() 在 -128 到 127 范围内缓存 Integer 对象——这就是享元模式。Integer a = 127; Integer b = 127; a == b 返回 true,但 Integer a = 128; Integer b = 128; a == b 返回 false。这个行为在什么场景下会导致微妙的 bug?
  3. String 常量池也是享元模式的应用。String.intern() 将字符串放入常量池并返回池中的引用。在 JDK 7 之前,常量池在永久代(PermGen),大量 intern() 可能导致 PermGen OOM。JDK 7+ 常量池移到了堆中——但 intern() 仍然有性能开销。在什么场景下使用 intern() 是值得的?