深夜凌晨两点,你还在办公室调试那个"看起来没问题"的代码,但就是跑不通测试用例。几小时前信心满满的重构,现在却像踩进了泥潭,改一处坏一处。这一刻,你不禁怀疑:我明明遵循了面向对象原则,为什么代码还是乱得像一盘意大利面?如果这个场景太过熟悉,那么恭喜你,你正在经历大多数程序员的必经之痛 —— 依赖地狱。
依赖管理:程序员最被低估的技能
每当我审查初级开发者的代码,总能看到这样的"杰作":一个类直接实例化它需要的其他类,这些被实例化的类又创建它们需要的对象,就这样层层嵌套下去,代码间的关系复杂得像蜘蛛网。修改任何一处,都可能引发连锁反应。更糟的是,单元测试几乎不可能进行,因为你无法隔离被测试的代码。
"这不是面向对象吗?为什么不能这样写?"很多人会问。
面向对象并不只是把代码放进类里那么简单。真正的面向对象是关于职责分离和依赖管理的艺术。而依赖注入(Dependency Injection,简称DI),正是这门艺术的核心技法。

什么是依赖注入?先忘掉那些晦涩定义
依赖注入听起来像某种高深技术,但本质上它是一个朴素的思想:不要在类内部创建依赖,而是从外部接收它们。
想象你去咖啡店点了一杯拿铁。传统编程方式就像咖啡师每次都自己跑去买牛奶、买咖啡豆、买杯子,甚至自己制造咖啡机。而依赖注入则是:店长提前准备好这些材料和工具,咖啡师只需要用这些"注入"的资源来制作咖啡。
让我们看个简单的代码对比:
没有依赖注入的代码:
public class OrderService {
private PaymentProcessor paymentProcessor;
private InventoryManager inventoryManager;
public OrderService() {
// 直接在内部创建依赖,强耦合!
this.paymentProcessor = new PayPalProcessor();
this.inventoryManager = new DatabaseInventoryManager();
}
public void processOrder(Order order) {
// 使用内部创建的依赖
if (inventoryManager.checkStock(order.getItems())) {
paymentProcessor.processPayment(order.getTotalAmount());
// ...其他逻辑
}
}
}
使用依赖注入的代码:
public class OrderService {
private final PaymentProcessor paymentProcessor;
private final InventoryManager inventoryManager;
// 通过构造函数注入依赖
public OrderService(PaymentProcessor paymentProcessor,
InventoryManager inventoryManager) {
this.paymentProcessor = paymentProcessor;
this.inventoryManager = inventoryManager;
}
public void processOrder(Order order) {
// 使用注入的依赖
if (inventoryManager.checkStock(order.getItems())) {
paymentProcessor.processPayment(order.getTotalAmount());
// ...其他逻辑
}
}
}
注意到区别了吗?在第二个例子中,OrderService不再关心具体使用哪种支付处理器或库存管理器,它只关心这些组件能完成特定的接口约定。这就是"依赖倒置原则"的实践——高层模块不应该依赖低层模块,两者都应该依赖抽象。

为什么依赖注入能拯救你的代码?
很多人误解依赖注入只是一种编码风格,而忽视了它带来的革命性变化。依赖注入本质上是对软件架构的深刻改变,它能够:
1. 让单元测试变得可行且简单
没有依赖注入时,测试OrderService就意味着也要测试PayPalProcessor和DatabaseInventoryManager。而有了依赖注入,你可以轻松创建这两个依赖的mock对象:
@Test
public void testProcessOrder_sufficientInventory() {
// 创建mock对象
PaymentProcessor mockPayment = mock(PaymentProcessor.class);
InventoryManager mockInventory = mock(InventoryManager.class);
// 设置mock行为
when(mockInventory.checkStock(any())).thenReturn(true);
// 创建被测试对象,注入mock依赖
OrderService service = new OrderService(mockPayment, mockInventory);
// 执行测试
Order testOrder = new Order(/*...*/);
service.processOrder(testOrder);
// 验证行为
verify(mockPayment).processPayment(testOrder.getTotalAmount());
}
2. 极大降低代码耦合度
想象一下,如果你需要将支付方式从PayPal改为Stripe,在没有依赖注入的系统中,你可能需要修改每一个直接实例化PayPalProcessor的地方。而在使用依赖注入的系统中,你只需要在配置依赖的地方做一处修改。
3. 促进更好的代码设计
当你习惯了依赖注入的思维,你会自然而然地思考每个类的职责边界,遵循单一职责原则,设计更加模块化的系统。这也是为什么常说"没有DI的面向对象,不过是更复杂的过程式代码"。
我曾见过一个电商项目,最初几乎所有业务逻辑都塞在控制器里,后来随着功能增多,代码变得无法维护。团队引入依赖注入后,强制将业务逻辑拆分为多个服务类,每个类都有明确的职责,代码质量和可维护性得到了质的提升。
从混乱到清晰:依赖注入实战改造
让我们通过一个电商系统的核心功能——订单处理流程,来演示依赖注入的威力。
阶段一:混沌初始代码
public class OrderProcessor {
public void processOrder(String userId, List<String> productIds, String address) {
// 直接创建依赖
UserRepository userRepo = new MySQLUserRepository();
ProductRepository productRepo = new MySQLProductRepository();
InventoryService inventory = new WarehouseInventoryService();
PaymentService payment = new StripePaymentService();
ShippingService shipping = new FedExShippingService();
EmailService emailService = new SmtpEmailService();
// 检查用户
User user = userRepo.findById(userId);
if (user == null) {
throw new UserNotFoundException("User not found");
}
// 获取产品并计算总价
double totalPrice = 0;
List<Product> products = new ArrayList<>();
for (String pid : productIds) {
Product p = productRepo.findById(pid);
if (p == null) {

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



