在阅读本章时,不是太明白,遂借助 deepseek 理解本章:
核心思想:构造与使用分离
这是本章最根本、最重要的理念。作者认为,系统混乱的主要根源在于:
构造(Construction) 和 使用(Use) 这两件事被混在了一起。
将这两者混杂,会导致代码职责不清、难以测试和维护。一个整洁的系统架构应该将这两者清晰地分离开。
关键策略与模式
1. 依赖注入(Dependency Injection, DI)
是什么:实现“构造与使用分离”的核心技术。对象不应自己创建其依赖,而应由外部容器将依赖“注入”给它(通常通过构造函数或属性)。
为什么:
控制反转(IoC):将创建依赖的控制权从类内部反转到了外部容器,降低耦合度。
极致可测试性:可以轻松注入“模拟对象(Mock)”进行单元测试。
灵活性:更换依赖实现(如换数据库)只需修改容器配置,而无需修改业务代码。
示例:
糟糕的写法(构造与使用混合):
1 2 3 4 5 6 7 8 9
| public class MyService { private MyRepository repository = new MyRepository();
public void doBusinessLogic() { repository.getData(); } }
|
问题:无法轻松测试 MyService
,因为它强耦合于真实的 MyRepository
。
整洁的写法(依赖注入):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyService { private MyRepository repository;
public MyService(MyRepository repository) { this.repository = repository; }
public void doBusinessLogic() { repository.getData(); } }
|
好处:MyService
不再关心 MyRepository
如何被创建。
2. 面向接口编程
是什么:依赖注入应依赖于接口(Interface),而非具体实现(Concrete Implementation)。
为什么:提供抽象和多态能力,让依赖的具体实现可以像插件一样被轻松替换。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface Repository { Data getData(); }
public class SqlRepository implements Repository { ... } public class InMemoryRepository implements Repository { ... }
public class MyService { private Repository repository;
public MyService(Repository repository) { this.repository = repository; } ... }
|
好处:不修改 MyService
的任何代码,即可切换不同的数据源实现。
3. 使用DI框架作为”主组件”
是什么:使用如Spring之类的框架作为应用程序的”主组件”(Main Component),由其负责整个系统的构造过程。
为什么:将构造系统的职责完全从业务代码中剥离,让业务代码只关注”使用”(即业务逻辑本身)。
工作流程:
声明:使用注解(如 @Component
, @Service
)标记你的类。
装配:使用注解(如 @Autowired
)声明依赖关系。
构造:DI容器在启动时读取所有元数据,承担所有”构造”责任,组装好整个对象图。
运行:你的业务代码直接使用已组装好的对象,纯粹地执行业务逻辑。
示例(简化Spring Boot):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Service public class CreditCardProcessor implements PaymentProcessor { public void process(Payment p) { ... } }
@Service public class ShoppingCartService { private final PaymentProcessor processor;
@Autowired public ShoppingCartService(PaymentProcessor processor) { this.processor = processor; }
public void checkout(Cart c) { ... processor.process(payment); } }
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
总结与启示
概念 |
启示 |
分离构造与使用 |
这是设计松散耦合、可维护系统的基石。你的业务代码不应该被 new 关键字污染。 |
使用DI和接口 |
这是实现上述分离的首要技术手段。它直接带来了可测试性,这是衡量设计好坏的重要标准。 |
框架是工具 |
Spring等DI框架不是目的,而是实现”构造与使用分离”这一目标的强大工具。它们扮演了”系统组装者”的角色。 |
架构目标 |
本章的最终目的是:管理复杂性。通过将构造的复杂性隔离到容器中,使得核心业务逻辑变得简单、清晰、易于变化。 |
一句话总结:
不要让你的业务代码(建筑师)去操心依赖如何被创建和组装(搬砖和水泥),而应该由一个专门的DI容器(施工队)根据配置(蓝图)来完成所有组装工作,然后将完整的、可用的依赖交付给业务代码去使用。
使用 Generic Host (推荐)
对于 C# 的 Console Application,使用依赖注入需要手动设置 DI 容器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Threading.Tasks;
public interface IMessageService { void SendMessage(string message); }
public class EmailService : IMessageService { public void SendMessage(string message) { Console.WriteLine($"发送邮件: {message}"); } }
public class NotificationManager { private readonly IMessageService _messageService;
public NotificationManager(IMessageService messageService) { _messageService = messageService; }
public void Notify(string message) { _messageService.SendMessage(message); } }
public class MyBackgroundService : BackgroundService { private readonly NotificationManager _notificationManager;
public MyBackgroundService(NotificationManager notificationManager) { _notificationManager = notificationManager; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _notificationManager.Notify($"消息时间: {DateTime.Now}"); await Task.Delay(1000, stoppingToken); } } }
class Program { static async Task Main(string[] args) { var host = Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { services.AddTransient<IMessageService, EmailService>(); services.AddTransient<NotificationManager>();
services.AddHostedService<MyBackgroundService>();
services.AddSingleton<IConfigService, ConfigService>(); }) .Build();
await host.RunAsync(); } }
public interface IConfigService { string GetConfigValue(string key); }
public class ConfigService : IConfigService { public string GetConfigValue(string key) { return $"Value for {key}"; } }
|
虽然 Generic Host 功能强大,但并非所有控制台应用都需要它。对于非常简单的、一次性的脚本任务,直接使用传统的写法可能更直接和轻量。