第2章:接缝技术(Seam Techniques)
一、导读
1.1 本章学习目标
第2章讲解接缝技术——Feathers 最核心的贡献。接缝是代码中可以插入测试或修改行为的点。
本章的核心学习目标包括:
- 理解接缝的概念和类型
- 掌握头文件接缝、链接接缝、预处理接缝
- 学会在 Java/C++ 中使用接缝
- 理解接缝与依赖分解的关系
1.2 为什么本章如此重要
接缝技术是处理遗留代码的核心工具。通过接缝,你可以在不修改原始代码的情况下替换依赖、插入测试、隔离行为。
1.3 核心问题引导
什么是接缝?它和"注入点"有什么区别?
接缝有哪些类型?各适用于什么场景?
如何识别代码中的接缝?
接缝如何帮助分解依赖?
二、核心概念详解
2.1 接缝的定义
接缝(Seam):程序中可以不改变行为而替换行为的点
接缝类型:
1. 预处理器接缝:通过预处理指令替换代码
2. 链接接缝:通过链接不同的实现替换代码
3. 对象接缝:通过多态替换对象行为
4. 子类接缝:通过继承替换行为2.2 对象接缝
java
// 遗留代码
public class OrderProcessor {
private EmailService emailService = new EmailService();
public void process(Order order) {
// ... 处理订单
emailService.sendConfirmation(order.getCustomer());
}
}
// 问题:EmailService 是硬编码的,无法测试
// 使用接缝:提取接口
public interface EmailSender {
void sendConfirmation(Customer customer);
}
public class OrderProcessor {
private EmailSender emailSender;
// 接缝:构造函数注入
public OrderProcessor(EmailSender emailSender) {
this.emailSender = emailSender;
}
// 保持向后兼容
public OrderProcessor() {
this(new EmailService());
}
public void process(Order order) {
// ... 处理订单
emailSender.sendConfirmation(order.getCustomer());
}
}
// 测试中使用 mock
@Test
void testProcess() {
List<String> sentEmails = new ArrayList<>();
EmailSender mockSender = customer -> sentEmails.add(customer.getEmail());
OrderProcessor processor = new OrderProcessor(mockSender);
processor.process(new Order(new Customer("test@example.com")));
assertEquals(1, sentEmails.size());
}2.3 子类接缝
java
// 遗留代码:无法修改
public class LegacySystem {
public String getData() {
// 调用外部服务,测试时不可用
return ExternalService.fetch();
}
}
// 使用子类接缝
public class TestableSystem extends LegacySystem {
private String testData;
public void setTestData(String data) {
this.testData = data;
}
@Override
public String getData() {
return testData != null ? testData : super.getData();
}
}
// 测试中使用
@Test
void testWithSubclass() {
TestableSystem system = new TestableSystem();
system.setTestData("test data");
// 现在可以测试了
}2.4 链接接缝
c
// 遗留 C 代码
void process() {
time_t now = time(NULL);
// ... 使用 now
}
// 链接接缝:替换 time 函数
// 生产代码链接系统 time
// 测试代码链接自定义 time
time_t custom_time(time_t *t) {
return test_time; // 可控的时间
}三、实战案例
3.1 案例一:识别和应用接缝
java
// 遗留代码
public class ReportGenerator {
public String generate() {
Connection conn = DriverManager.getConnection(DB_URL); // 硬编码依赖
Calendar cal = Calendar.getInstance(); // 时间依赖
String user = System.getProperty("user.name"); // 环境依赖
// ...
}
}
// 识别接缝:
// 1. DriverManager.getConnection → 数据库接缝
// 2. Calendar.getInstance → 时间接缝
// 3. System.getProperty → 环境接缝
// 应用接缝
public class ReportGenerator {
private DataSource dataSource;
private Clock clock;
private Environment env;
public ReportGenerator(DataSource ds, Clock clock, Environment env) {
this.dataSource = ds;
this.clock = clock;
this.env = env;
}
// 默认构造函数保持兼容
public ReportGenerator() {
this(new JdbcDataSource(DB_URL), SystemClock.INSTANCE, SystemEnvironment.INSTANCE);
}
}四、本章小结
接缝:可以不改变行为而替换行为的点。
对象接缝:通过接口和多态替换依赖。
子类接缝:通过继承覆盖方法。
链接接缝:通过链接不同的实现。
五、思考与练习
在一个遗留系统中找出至少5个接缝。
子类接缝有什么风险?如何最小化这些风险?
如何在不修改原始代码的情况下添加接缝?