第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 的典型模式
模式一:直接实例化具体类
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);
}
}模式二:静态方法调用
public class OrderService {
public void processOrder(Order order) {
// 违反 DIP:静态方法调用是硬编码的依赖
DatabaseHelper.save(order);
EmailUtils.sendConfirmation(order);
}
}模式三:单例模式滥用
public class OrderService {
public void processOrder(Order order) {
// 违反 DIP:全局单例是隐藏的依赖
Database.getInstance().save(order);
EmailSender.getInstance().send(order);
}
}2.4 实现 DIP 的技术
技术一:构造函数注入
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 注入
public class OrderService {
private OrderRepository repository;
public void setRepository(OrderRepository repository) {
this.repository = repository;
}
}适用于可选依赖或循环依赖的情况。
技术三:接口注入
public interface RepositoryInjectable {
void injectRepository(OrderRepository repository);
}
public class OrderService implements RepositoryInjectable {
private OrderRepository repository;
@Override
public void injectRepository(OrderRepository repository) {
this.repository = repository;
}
}较少使用,主要用于框架级别的依赖注入。
技术四:服务定位器
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 框架
手动依赖组装:
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):
@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
/**
* 初始版本:紧耦合,违反 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 实现
- 无法在不修改代码的情况下切换支付网关
- 测试需要真实的网络和数据库
- 配置硬编码在代码中
/**
* 重构版本:应用 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)
/**
* 使用 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 示例三:函数式编程中的依赖倒置
"""
在函数式编程中实现依赖倒置
函数式编程通过高阶函数和闭包实现依赖倒置,
不需要接口或类。
"""
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 的哪些方面,并提出改进方案:
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 中的效应系统如何管理依赖。
批判性思考:在什么情况下,直接使用具体类可能是合理的?给出具体场景。