7架构整洁之道

依赖倒置原则

DIP

依赖方向抽象策略依赖注入

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

第7章 依赖倒置原则 - DIP (Dependency Inversion Principle)

一、导读

1.1 本章学习目标

通过本章的学习,你将能够:

深入理解依赖倒置原则(DIP)的核心思想及其在软件设计中的重要性

区分依赖倒置与依赖注入的概念

掌握通过抽象解耦高层策略和低层细节的技术

识别代码中违反 DIP 的模式,特别是直接实例化具体类

学会使用依赖注入、服务定位器等模式实现 DIP

理解 DIP 在分层架构和整洁架构中的应用

掌握手动构建依赖图和DI框架的使用

理解 DIP 与其他 SOLID 原则的关系

1.2 为什么本章如此重要

依赖倒置原则是 SOLID 原则中关于模块间关系的关键原则。它揭示了一个反直觉的设计洞察:传统的过程式设计中,高层策略依赖低层实现;而在良好的面向对象设计中,两者都应该依赖抽象,且低层实现反而依赖高层定义的抽象。

在实际开发中,违反 DIP 是导致代码紧耦合、难以测试、难以扩展的主要原因之一。当高层业务逻辑直接依赖具体的数据库实现、HTTP 客户端或第三方库时,替换这些底层细节变得极其困难,单元测试也变得复杂(需要真实的数据库或网络)。

DIP 提供了一种机制,通过将依赖方向反转,使高层策略独立于低层细节。这不仅提高了代码的可测试性和可维护性,还促进了插件式架构和清晰的架构边界。

依赖注入(DI)是实现 DIP 的一种常见技术,但 DIP 本身是一个更广泛的设计原则。理解这一点有助于我们正确使用 DI 框架,而不是将其视为目的本身。

1.3 核心问题引导

在深入本章内容之前,请思考以下问题:

什么是"依赖倒置"?它与传统的依赖方向有什么区别?

依赖倒置和依赖注入是一回事吗?如果不是,它们有什么关系?

为什么高层模块不应该依赖低层模块?

抽象如何帮助解耦模块间的依赖?

在没有 DI 框架的情况下,如何实现 DIP?

DIP 在分层架构中如何指导层间依赖的方向?

过度使用 DIP 会导致什么问题?

函数式编程如何实现依赖倒置?


二、核心概念详解

2.1 DIP 的精确定义

依赖倒置原则的正式表述包含两个部分:

高层模块不应该依赖低层模块,两者都应该依赖抽象

抽象不应该依赖细节,细节应该依赖抽象

这个定义中有几个关键概念需要澄清:

高层模块(High-level Modules):包含系统的核心业务逻辑、策略和规则。它们代表了系统的主要行为和决策逻辑。例如,订单处理逻辑、用户认证流程、业务规则引擎等。

低层模块(Low-level Modules):包含具体的实现细节,如数据库访问、网络通信、文件操作、第三方 API 调用等。它们为高层模块提供基础设施支持。

抽象(Abstractions):通常是接口或抽象类,定义了行为的契约而不指定具体实现。

细节(Details):抽象的具体实现,包含实际的工作逻辑。

在传统的过程式设计中,依赖方向是从上到下的:高层模块直接调用低层模块。DIP 要求反转这种依赖方向:高层模块定义它需要的抽象接口,低层模块实现这些接口。这样,高层模块不依赖具体的低层实现,而是依赖自己定义的抽象;低层模块反而依赖高层定义的抽象。

2.2 传统依赖 vs 倒置依赖

传统依赖方向:

[OrderService] --> [MySQLDatabase]
       |
       v
[EmailSender] --> [SmtpClient]

高层模块直接依赖低层模块。问题:

  • 替换数据库需要修改 OrderService
  • 测试 OrderService 需要真实的数据库
  • OrderService 无法独立于 MySQL 演进

倒置依赖方向:

[OrderService] --> [OrderRepository] <-- [MySqlOrderRepository]
       |
       v
[NotificationService] --> [MessageSender] <-- [EmailSender]

高层模块定义抽象,低层模块实现抽象。好处:

  • 替换数据库只需提供新的 OrderRepository 实现
  • 测试 OrderService 可以注入 mock OrderRepository
  • OrderService 可以独立于具体数据库演进

2.3 违反 DIP 的典型模式

模式一:直接实例化具体类

java
public class OrderService {
    // 违反 DIP:直接依赖具体实现
    private final MySQLDatabase database = new MySQLDatabase();
    private final SmtpEmailSender emailSender = new SmtpEmailSender();
    
    public void processOrder(Order order) {
        database.save(order);
        emailSender.sendConfirmation(order);
    }
}

模式二:静态方法调用

java
public class OrderService {
    public void processOrder(Order order) {
        // 违反 DIP:静态方法调用是硬编码的依赖
        DatabaseHelper.save(order);
        EmailUtils.sendConfirmation(order);
    }
}

模式三:单例模式滥用

java
public class OrderService {
    public void processOrder(Order order) {
        // 违反 DIP:全局单例是隐藏的依赖
        Database.getInstance().save(order);
        EmailSender.getInstance().send(order);
    }
}

2.4 实现 DIP 的技术

技术一:构造函数注入

java
public class OrderService {
    private final OrderRepository repository;
    private final NotificationService notificationService;
    
    // 依赖通过构造函数注入
    public OrderService(OrderRepository repository, 
                       NotificationService notificationService) {
        this.repository = repository;
        this.notificationService = notificationService;
    }
}

这是最推荐的注入方式,因为:

  • 依赖关系清晰可见
  • 可以声明为 final,确保不可变
  • 便于单元测试

技术二:Setter 注入

java
public class OrderService {
    private OrderRepository repository;
    
    public void setRepository(OrderRepository repository) {
        this.repository = repository;
    }
}

适用于可选依赖或循环依赖的情况。

技术三:接口注入

java
public interface RepositoryInjectable {
    void injectRepository(OrderRepository repository);
}

public class OrderService implements RepositoryInjectable {
    private OrderRepository repository;
    
    @Override
    public void injectRepository(OrderRepository repository) {
        this.repository = repository;
    }
}

较少使用,主要用于框架级别的依赖注入。

技术四:服务定位器

java
public class ServiceLocator {
    private static final Map<Class<?>, Object> services = new HashMap<>();
    
    public static <T> T get(Class<T> type) {
        return type.cast(services.get(type));
    }
    
    public static void register(Class<?> type, Object instance) {
        services.put(type, instance);
    }
}

public class OrderService {
    private final OrderRepository repository;
    
    public OrderService() {
        this.repository = ServiceLocator.get(OrderRepository.class);
    }
}

服务定位器是一种反模式,因为它隐藏了依赖关系,不推荐在新代码中使用。

2.5 手动依赖组装 vs DI 框架

手动依赖组装:

java
public class Application {
    public static void main(String[] args) {
        // 创建低层实现
        DataSource dataSource = createDataSource();
        OrderRepository repository = new JdbcOrderRepository(dataSource);
        NotificationService notifier = new EmailNotificationService();
        
        // 注入到高层模块
        OrderService orderService = new OrderService(repository, notifier);
        
        // 使用
        orderService.processOrder(createTestOrder());
    }
}

优点:

  • 简单明了,没有框架开销
  • 依赖关系完全可控
  • 易于理解和调试

缺点:

  • 大型项目中组装代码可能变得复杂
  • 需要手动管理生命周期

DI 框架(如 Spring):

java
@Component
public class OrderService {
    private final OrderRepository repository;
    private final NotificationService notifier;
    
    @Autowired
    public OrderService(OrderRepository repository, 
                       NotificationService notifier) {
        this.repository = repository;
        this.notifier = notifier;
    }
}

@Configuration
public class AppConfig {
    @Bean
    public OrderRepository orderRepository(DataSource dataSource) {
        return new JdbcOrderRepository(dataSource);
    }
    
    @Bean
    public NotificationService notificationService() {
        return new EmailNotificationService();
    }
}

优点:

  • 自动管理依赖组装
  • 支持作用域和生命周期管理
  • 丰富的生态系统

缺点:

  • 学习曲线
  • 运行时错误难以调试
  • 可能鼓励过度设计

2.6 DIP 在架构中的应用

分层架构:

在传统的三层架构中,依赖方向通常是从上到下:表现层 -> 业务层 -> 数据层。应用 DIP 后,业务层定义数据访问接口,数据层实现这些接口。

[表现层] --> [业务层接口] <-- [数据层实现]
                ^
                |
           [业务层实现]

整洁架构:

在整洁架构中,依赖规则是:所有依赖都指向内层。外层(如数据库、UI、外部 API)依赖内层(业务逻辑)定义的接口。

[Entities] <-- [Use Cases] <-- [Interface Adapters] <-- [Frameworks & Drivers]
     ^              ^                  ^                       ^
     |              |                  |                       |
  定义接口      定义接口           实现接口                 实现接口

六边形架构(端口和适配器):

端口是业务逻辑定义的接口,适配器是具体实现。依赖方向是从适配器指向端口。

[核心业务逻辑]
     |
     | 定义端口(接口)
     v
[输入端口] <-- [输入适配器](如 REST Controller)
[输出端口] <-- [输出适配器](如 Database Repository)

2.7 DIP 与其他原则的关系

DIP 与 SRP:分离职责后,不同职责之间的依赖需要通过抽象来管理。

DIP 与 OCP:依赖倒置为实现开闭原则提供了机制。通过依赖抽象,可以在不修改高层代码的情况下替换低层实现。

DIP 与 LSP:如果具体实现不能正确替代抽象(违反 LSP),依赖倒置就失去了意义。

DIP 与 ISP:小而专一的接口使依赖更加精确,减少不必要的耦合。


三、程序员的视角

3.1 在实际项目中的应用

在日常工作中,应用 DIP 可以从以下几个方面入手:

设计新功能时:

  • 首先定义高层模块需要的接口
  • 然后实现具体的低层模块
  • 通过构造函数注入依赖

代码审查时:

  • 检查是否有 new 关键字创建具体类
  • 注意静态方法调用
  • 寻找硬编码的配置或路径

重构遗留代码时:

  • 从最核心的业务逻辑开始
  • 提取接口,将具体实现移到外面
  • 逐步引入依赖注入

3.2 常见陷阱与误区

陷阱一:将 DIP 等同于 DI 框架

DIP 是一个设计原则,DI 是实现它的一种技术。即使没有 Spring 等框架,也可以通过手动构造对象图来实现 DIP。

陷阱二:过度注入

将所有东西都通过注入传递,包括简单的值对象或工具类。这增加了复杂性而没有带来好处。

陷阱三:循环依赖

A 依赖 B,B 依赖 A。这通常表明设计有问题,需要重新审视职责划分。

陷阱四:泄漏的抽象

接口暴露了实现细节,导致客户端仍然依赖具体实现的特性。

3.3 与其他层级的联系

DIP 与软件工程的其他方面密切相关:

  • 测试:符合 DIP 的代码更容易单元测试,因为可以注入 mock
  • 部署:松耦合的模块可以独立部署
  • 团队协作:清晰的接口使并行开发更容易

四、代码实战

4.1 示例一:从紧耦合到 DIP

java
/**
 * 初始版本:紧耦合,违反 DIP
 */
public class PaymentProcessor {
    
    private final StripeApiClient stripeClient;
    private final MySqlDatabase database;
    private final SmtpMailer mailer;
    
    public PaymentProcessor() {
        this.stripeClient = new StripeApiClient("sk_test_...");
        this.database = new MySqlDatabase("jdbc:mysql://localhost/payments");
        this.mailer = new SmtpMailer("smtp.company.com", 587);
    }
    
    public PaymentResult processPayment(PaymentRequest request) {
        // 验证
        if (!validateRequest(request)) {
            throw new InvalidRequestException("Invalid payment request");
        }
        
        // 调用 Stripe API
        StripeCharge charge = stripeClient.charge(
            request.getAmount(),
            request.getCurrency(),
            request.getToken()
        );
        
        // 保存到数据库
        PaymentRecord record = new PaymentRecord();
        record.setAmount(request.getAmount());
        record.setCurrency(request.getCurrency());
        record.setStripeChargeId(charge.getId());
        record.setStatus("COMPLETED");
        database.save(record);
        
        // 发送确认邮件
        mailer.send(
            request.getEmail(),
            "Payment Confirmation",
            "Your payment of " + request.getAmount() + " " + request.getCurrency() + " was successful."
        );
        
        return new PaymentResult(true, charge.getId());
    }
    
    private boolean validateRequest(PaymentRequest request) {
        return request.getAmount() > 0 && request.getToken() != null;
    }
}

这个类的问题:

  • 直接依赖具体的 Stripe、MySQL、SMTP 实现
  • 无法在不修改代码的情况下切换支付网关
  • 测试需要真实的网络和数据库
  • 配置硬编码在代码中
java
/**
 * 重构版本:应用 DIP
 */

// === 抽象:高层模块定义的接口 ===

public interface PaymentGateway {
    PaymentCharge charge(double amount, String currency, String token);
    PaymentCharge refund(String chargeId, double amount);
}

public interface PaymentRepository {
    void save(PaymentRecord record);
    Optional<PaymentRecord> findByChargeId(String chargeId);
}

public interface NotificationService {
    void sendPaymentConfirmation(String email, double amount, String currency);
}

// === 高层模块:只依赖抽象 ===

public class PaymentProcessor {
    
    private final PaymentGateway paymentGateway;
    private final PaymentRepository repository;
    private final NotificationService notifier;
    
    // 依赖通过构造函数注入
    public PaymentProcessor(PaymentGateway paymentGateway,
                           PaymentRepository repository,
                           NotificationService notifier) {
        this.paymentGateway = paymentGateway;
        this.repository = repository;
        this.notifier = notifier;
    }
    
    public PaymentResult processPayment(PaymentRequest request) {
        validateRequest(request);
        
        // 通过抽象调用支付网关
        PaymentCharge charge = paymentGateway.charge(
            request.getAmount(),
            request.getCurrency(),
            request.getToken()
        );
        
        // 通过抽象保存记录
        PaymentRecord record = PaymentRecord.builder()
            .amount(request.getAmount())
            .currency(request.getCurrency())
            .chargeId(charge.getId())
            .status("COMPLETED")
            .build();
        repository.save(record);
        
        // 通过抽象发送通知
        notifier.sendPaymentConfirmation(
            request.getEmail(),
            request.getAmount(),
            request.getCurrency()
        );
        
        return new PaymentResult(true, charge.getId());
    }
    
    private void validateRequest(PaymentRequest request) {
        if (request.getAmount() <= 0) {
            throw new InvalidRequestException("Amount must be positive");
        }
        if (request.getToken() == null) {
            throw new InvalidRequestException("Payment token is required");
        }
    }
}

// === 低层实现:依赖高层定义的抽象 ===

public class StripePaymentGateway implements PaymentGateway {
    
    private final String apiKey;
    private final HttpClient httpClient;
    
    public StripePaymentGateway(String apiKey, HttpClient httpClient) {
        this.apiKey = apiKey;
        this.httpClient = httpClient;
    }
    
    @Override
    public PaymentCharge charge(double amount, String currency, String token) {
        // 调用 Stripe API
        Map<String, Object> params = new HashMap<>();
        params.put("amount", (long) (amount * 100));
        params.put("currency", currency);
        params.put("source", token);
        
        HttpResponse response = httpClient.post(
            "https://api.stripe.com/v1/charges",
            params,
            Map.of("Authorization", "Bearer " + apiKey)
        );
        
        return parseChargeResponse(response);
    }
    
    @Override
    public PaymentCharge refund(String chargeId, double amount) {
        // 实现退款
        return null;
    }
    
    private PaymentCharge parseChargeResponse(HttpResponse response) {
        // 解析 JSON 响应
        return new PaymentCharge("ch_123", "succeeded");
    }
}

public class JdbcPaymentRepository implements PaymentRepository {
    
    private final DataSource dataSource;
    
    public JdbcPaymentRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public void save(PaymentRecord record) {
        try (Connection conn = dataSource.getConnection()) {
            PreparedStatement stmt = conn.prepareStatement(
                "INSERT INTO payments (amount, currency, charge_id, status) VALUES (?, ?, ?, ?)"
            );
            stmt.setDouble(1, record.getAmount());
            stmt.setString(2, record.getCurrency());
            stmt.setString(3, record.getChargeId());
            stmt.setString(4, record.getStatus());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new DataAccessException("Failed to save payment", e);
        }
    }
    
    @Override
    public Optional<PaymentRecord> findByChargeId(String chargeId) {
        // 实现查询
        return Optional.empty();
    }
}

public class EmailNotificationService implements NotificationService {
    
    private final EmailClient emailClient;
    
    public EmailNotificationService(EmailClient emailClient) {
        this.emailClient = emailClient;
    }
    
    @Override
    public void sendPaymentConfirmation(String email, double amount, String currency) {
        String subject = "Payment Confirmation";
        String body = String.format(
            "Your payment of %.2f %s was successful.", amount, currency
        );
        emailClient.send(email, subject, body);
    }
}

// === 依赖组装(Composition Root)===

public class Application {
    
    public static void main(String[] args) {
        // 创建低层实现
        HttpClient httpClient = new ApacheHttpClient();
        DataSource dataSource = createDataSource();
        EmailClient emailClient = new SendGridEmailClient("SG.xxx");
        
        // 构建依赖图
        PaymentGateway gateway = new StripePaymentGateway("sk_test_...", httpClient);
        PaymentRepository repository = new JdbcPaymentRepository(dataSource);
        NotificationService notifier = new EmailNotificationService(emailClient);
        
        // 注入到高层模块
        PaymentProcessor processor = new PaymentProcessor(gateway, repository, notifier);
        
        // 使用
        PaymentRequest request = new PaymentRequest(99.99, "USD", "tok_visa", "user@example.com");
        PaymentResult result = processor.processPayment(request);
        
        System.out.println("Payment result: " + result);
    }
    
    private static DataSource createDataSource() {
        // 创建数据源
        return new HikariDataSource();
    }
}

4.2 示例二:使用 DI 框架(Spring)

java
/**
 * 使用 Spring Framework 实现 DIP
 */

// === 接口定义 ===

public interface PaymentGateway {
    PaymentCharge charge(double amount, String currency, String token);
}

public interface PaymentRepository {
    void save(PaymentRecord record);
}

public interface NotificationService {
    void sendPaymentConfirmation(String email, double amount, String currency);
}

// === 高层模块 ===

@Service
public class PaymentProcessor {
    
    private final PaymentGateway paymentGateway;
    private final PaymentRepository repository;
    private final NotificationService notifier;
    
    @Autowired
    public PaymentProcessor(PaymentGateway paymentGateway,
                           PaymentRepository repository,
                           NotificationService notifier) {
        this.paymentGateway = paymentGateway;
        this.repository = repository;
        this.notifier = notifier;
    }
    
    @Transactional
    public PaymentResult processPayment(PaymentRequest request) {
        // 业务逻辑
        PaymentCharge charge = paymentGateway.charge(
            request.getAmount(),
            request.getCurrency(),
            request.getToken()
        );
        
        PaymentRecord record = PaymentRecord.builder()
            .amount(request.getAmount())
            .currency(request.getCurrency())
            .chargeId(charge.getId())
            .status("COMPLETED")
            .build();
        repository.save(record);
        
        notifier.sendPaymentConfirmation(
            request.getEmail(),
            request.getAmount(),
            request.getCurrency()
        );
        
        return new PaymentResult(true, charge.getId());
    }
}

// === 低层实现 ===

@Component
@Primary  // 默认实现
public class StripePaymentGateway implements PaymentGateway {
    
    @Value("${stripe.api-key}")
    private String apiKey;
    
    @Override
    public PaymentCharge charge(double amount, String currency, String token) {
        // 调用 Stripe API
        return new PaymentCharge("ch_123", "succeeded");
    }
}

@Component
@Profile("test")  // 测试环境使用 mock
public class MockPaymentGateway implements PaymentGateway {
    
    @Override
    public PaymentCharge charge(double amount, String currency, String token) {
        return new PaymentCharge("mock_ch_" + System.currentTimeMillis(), "succeeded");
    }
}

@Repository
public class JdbcPaymentRepository implements PaymentRepository {
    
    private final JdbcTemplate jdbcTemplate;
    
    @Autowired
    public JdbcPaymentRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public void save(PaymentRecord record) {
        jdbcTemplate.update(
            "INSERT INTO payments (amount, currency, charge_id, status) VALUES (?, ?, ?, ?)",
            record.getAmount(),
            record.getCurrency(),
            record.getChargeId(),
            record.getStatus()
        );
    }
}

@Service
public class EmailNotificationService implements NotificationService {
    
    @Override
    public void sendPaymentConfirmation(String email, double amount, String currency) {
        // 发送邮件
    }
}

// === 配置类 ===

@Configuration
@ComponentScan(basePackages = "com.example.payment")
public class PaymentConfig {
    
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
}

4.3 示例三:函数式编程中的依赖倒置

python
"""
在函数式编程中实现依赖倒置

函数式编程通过高阶函数和闭包实现依赖倒置,
不需要接口或类。
"""

from typing import Callable, Dict, Any
from dataclasses import dataclass


# === 依赖作为函数参数 ===

def create_payment_processor(
    charge_fn: Callable[[float, str, str], Dict[str, Any]],
    save_fn: Callable[[Dict[str, Any]], None],
    notify_fn: Callable[[str, float, str], None]
) -> Callable[[Dict[str, Any]], Dict[str, Any]]:
    """
    创建支付处理器
    
    依赖通过参数注入,而不是硬编码。
    """
    
    def process_payment(request: Dict[str, Any]) -> Dict[str, Any]:
        # 验证
        if request['amount'] <= 0:
            raise ValueError("Amount must be positive")
        
        # 调用支付网关
        charge_result = charge_fn(
            request['amount'],
            request['currency'],
            request['token']
        )
        
        # 保存记录
        record = {
            'amount': request['amount'],
            'currency': request['currency'],
            'charge_id': charge_result['id'],
            'status': 'COMPLETED'
        }
        save_fn(record)
        
        # 发送通知
        notify_fn(
            request['email'],
            request['amount'],
            request['currency']
        )
        
        return {
            'success': True,
            'charge_id': charge_result['id']
        }
    
    return process_payment


# === 具体实现 ===

def stripe_charge(amount: float, currency: str, token: str) -> Dict[str, Any]:
    """Stripe 支付实现"""
    # 调用 Stripe API
    return {'id': 'ch_123', 'status': 'succeeded'}


def mock_charge(amount: float, currency: str, token: str) -> Dict[str, Any]:
    """Mock 支付实现(用于测试)"""
    return {'id': f'mock_ch_{int(time.time())}', 'status': 'succeeded'}


def save_to_database(record: Dict[str, Any]) -> None:
    """保存到数据库"""
    print(f"Saving record: {record}")


def save_to_memory(records: list) -> Callable[[Dict[str, Any]], None]:
    """保存到内存(用于测试)"""
    def _save(record: Dict[str, Any]) -> None:
        records.append(record)
    return _save


def send_email(email: str, amount: float, currency: str) -> None:
    """发送邮件通知"""
    print(f"Sending email to {email}: Payment of {amount} {currency}")


def noop_notify(email: str, amount: float, currency: str) -> None:
    """空通知(用于测试)"""
    pass


# === 使用示例 ===

def main():
    # 生产环境配置
    production_processor = create_payment_processor(
        charge_fn=stripe_charge,
        save_fn=save_to_database,
        notify_fn=send_email
    )
    
    # 测试环境配置
    test_records = []
    test_processor = create_payment_processor(
        charge_fn=mock_charge,
        save_fn=save_to_memory(test_records),
        notify_fn=noop_notify
    )
    
    # 使用测试处理器
    request = {
        'amount': 99.99,
        'currency': 'USD',
        'token': 'tok_visa',
        'email': 'user@example.com'
    }
    
    result = test_processor(request)
    print(f"Result: {result}")
    print(f"Records: {test_records}")


if __name__ == "__main__":
    import time
    main()

五、深入思考

5.1 历史背景

依赖倒置原则由 Robert C. Martin 在 1990 年代末提出。它的思想根源可以追溯到更早的控制反转(Inversion of Control, IoC)概念,后者由 Martin Fowler 在 2004 年的文章中普及。

有趣的是,"依赖倒置"这个名字经常引起混淆。它不是指"颠倒依赖",而是指依赖方向的倒置:从传统的"高层依赖低层"变为"两者都依赖抽象"。

5.2 前沿方向

在现代软件工程中,DIP 的应用正在演变:

函数式依赖注入:使用高阶函数和闭包代替类和接口。

效应系统:在纯函数式语言中,使用效应系统(如 Haskell 的 Monad)管理依赖。

服务网格:在微服务架构中,服务网格(如 Istio)在基础设施层面实现依赖管理。

5.3 延伸阅读

  • 《Clean Architecture》- Robert C. Martin:DIP 在架构层面的应用
  • 《Dependency Injection in .NET》- Mark Seemann:DI 的深入讲解
  • 《Functional Programming in Scala》- Chiusano & Bjarnason:函数式依赖管理

六、实验笔记

实验一:重构紧耦合代码为 DIP 合规

目标:将一个紧耦合的类重构为符合 DIP 的结构。

步骤:

选择一个直接依赖具体实现的类

识别其依赖的低层模块

为每个依赖定义接口

修改类以通过构造函数接收依赖

创建具体实现类

编写依赖组装代码

验证所有测试通过

实验二:比较手动 DI 和框架 DI

目标:对同一功能分别使用手动依赖组装和 Spring DI 实现,比较两者的优缺点。


七、本章小结

本章深入探讨了依赖倒置原则(DIP),这是关于模块间依赖关系的原则。我们学习了:

DIP 要求高层模块和低层模块都依赖抽象,且细节依赖抽象。这反转了传统的依赖方向,使高层策略独立于低层细节。

依赖注入是实现 DIP 的常见技术,但 DIP 本身是一个更广泛的设计原则。构造函数注入是最推荐的方式。

手动依赖组装适合小型项目,DI 框架适合大型项目。选择取决于项目规模和团队熟悉度。

DIP 在分层架构、整洁架构、六边形架构中都有重要应用,是良好架构的基础。

DIP 与其他 SOLID 原则密切相关,共同构成良好的设计基础。


八、习题

概念理解:解释依赖倒置和依赖注入的区别与联系。

代码分析:分析以下代码违反了 DIP 的哪些方面,并提出改进方案:

java
public class UserService {
    private JdbcTemplate jdbc = new JdbcTemplate(getDataSource());
    
    public User findById(String id) {
        return jdbc.queryForObject("SELECT * FROM users WHERE id = ?", id);
    }
}

设计练习:为一个电商系统设计符合 DIP 的架构,包括商品管理、订单处理、支付等模块。

重构实践:将一个使用静态方法的类重构为使用依赖注入。

研究题:调研 Haskell 或 Scala 中的效应系统如何管理依赖。

批判性思考:在什么情况下,直接使用具体类可能是合理的?给出具体场景。

7/12依赖倒置
0%