5Growing Object-Oriented Software, Guided by Tests

对象设计

用测试驱动设计

职责驱动依赖注入接口隔离值对象

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:对象设计(Object Design)

一、导读

1.1 本章学习目标

第5章讲解如何用测试驱动对象设计。通过测试来发现设计问题,推动更好的抽象。

1.2 为什么第五章如此重要

TDD 的核心价值不是测试,而是设计。测试是发现设计问题的工具。

1.3 核心问题引导

如何通过测试发现设计问题?

职责驱动设计是什么?

依赖注入如何改善设计?

值对象有什么特殊之处?


二、核心概念详解

5.1 职责驱动设计

设计原则:
- 每个对象负责一件事
- 对象之间通过消息通信
- 高内聚、低耦合

测试揭示设计问题:
- 测试难以编写 → 职责不清
- 需要太多 Mock → 依赖太多
- 测试脆弱 → 接口不稳定

5.2 依赖注入

java
// 反模式:硬编码依赖
public class OrderService {
    private PaymentGateway gateway = new StripeGateway();
}

// 正确做法:依赖注入
public class OrderService {
    private final PaymentGateway gateway;
    
    public OrderService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

// 测试时可以注入 Mock
OrderService service = new OrderService(mockGateway);

5.3 值对象

java
// 值对象:通过值相等,不可变
public final class Money {
    private final BigDecimal amount;
    private final Currency currency;
    
    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money)) return false;
        Money m = (Money) o;
        return amount.equals(m.amount) && currency.equals(m.currency);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}

三、实战案例

5.1 测试驱动设计

java
// 测试难以编写 → 设计有问题
@Test
public void shouldCreateOrder() {
    // 需要创建数据库、缓存、消息队列...
    // 说明 OrderService 职责太多
}

// 重构:拆分职责
@Test
public void shouldCreateOrder() {
    OrderService service = new OrderService(
        mockRepo, mockCache, mockEventBus);
    service.create(order);
    verify(mockRepo).save(order);
    verify(mockEventBus).publish(new OrderCreated(order));
}
// 测试容易编写 → 设计更清晰

四、本章小结

测试是设计的工具。通过测试可以发现设计问题,推动更好的抽象。职责驱动设计、依赖注入和值对象是 TDD 中常用的设计技术。


五、思考与练习

如何通过测试发现设计问题?

依赖注入有什么优势?

值对象和实体有什么区别?

如何平衡测试的简洁性和覆盖度?

如何用测试驱动接口设计?


5/7对象设计
0%