面试题答案
一键面试接口隔离原则与依赖倒置原则关系分析
1. 接口隔离原则(ISP)
- 定义:客户端不应该依赖它不需要的接口。即一个类对另一个类的依赖应该建立在最小的接口上。
- 作用:避免臃肿的接口导致的耦合问题,提高系统的内聚性和可维护性。当接口发生变化时,不会影响到那些只依赖部分接口功能的客户端。
2. 依赖倒置原则(DIP)
- 定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
- 作用:降低模块间的耦合度,使得高层模块和低层模块的依赖关系更加灵活,便于系统的扩展和维护。
3. 二者协同关系
- 协同工作提高可维护性:ISP通过将大接口拆分为小接口,减少了模块间不必要的依赖,使得代码结构更加清晰,维护时可以聚焦于更小的接口和模块。DIP让模块依赖抽象而非具体实现,当具体实现发生变化时,只要抽象接口不变,依赖它的模块无需修改,进一步提高了可维护性。
- 协同工作提高可扩展性:ISP保证了接口的粒度合适,为新功能的添加提供了清晰的接口边界。DIP使得高层模块基于抽象编程,新的低层模块只要实现了相应的抽象接口,就可以很方便地接入系统,提高了系统的扩展性。
- 协同工作提高可测试性:ISP拆分后的小接口更容易进行单元测试,因为每个接口的功能单一,测试逻辑相对简单。DIP让模块依赖抽象,在测试时可以通过创建抽象接口的模拟实现(如使用Mock对象),方便地对依赖该抽象的模块进行测试,提高了可测试性。
复杂业务场景下的架构设计示例及代码实现
业务场景
假设我们正在开发一个电商系统,涉及商品管理、订单处理、用户管理等多个模块。以订单处理为例,订单处理模块需要与商品模块交互获取商品价格,与用户模块交互获取用户信息等。
架构设计
1. 定义抽象接口
// 商品信息抽象接口
interface IProductInfo {
double getPrice(String productId);
}
// 用户信息抽象接口
interface IUserInfo {
String getUserName(String userId);
}
// 订单处理抽象接口
interface IOrderProcessor {
void processOrder(String userId, String productId, int quantity);
}
2. 具体模块实现抽象接口
// 商品模块实现商品信息接口
class ProductModule implements IProductInfo {
@Override
public double getPrice(String productId) {
// 实际逻辑:从数据库或其他数据源获取商品价格
return 100.0;
}
}
// 用户模块实现用户信息接口
class UserModule implements IUserInfo {
@Override
public String getUserName(String userId) {
// 实际逻辑:从数据库或其他数据源获取用户名称
return "John Doe";
}
}
// 订单处理模块依赖商品和用户信息抽象接口
class OrderProcessor implements IOrderProcessor {
private IProductInfo productInfo;
private IUserInfo userInfo;
public OrderProcessor(IProductInfo productInfo, IUserInfo userInfo) {
this.productInfo = productInfo;
this.userInfo = userInfo;
}
@Override
public void processOrder(String userId, String productId, int quantity) {
double price = productInfo.getPrice(productId);
String userName = userInfo.getUserName(userId);
double total = price * quantity;
System.out.println("Processing order for user " + userName + " with total amount: " + total);
}
}
3. 高层模块使用抽象接口
public class EcommerceSystem {
public static void main(String[] args) {
IProductInfo productInfo = new ProductModule();
IUserInfo userInfo = new UserModule();
IOrderProcessor orderProcessor = new OrderProcessor(productInfo, userInfo);
orderProcessor.processOrder("1", "P001", 2);
}
}
在这个示例中,订单处理模块(OrderProcessor
)遵循依赖倒置原则,依赖IProductInfo
和IUserInfo
抽象接口,而不是具体的ProductModule
和UserModule
。同时,IProductInfo
、IUserInfo
和IOrderProcessor
这些接口遵循接口隔离原则,只提供必要的方法,使得各个模块之间的依赖关系清晰,提高了系统的可维护性、可扩展性和可测试性。例如,如果需要修改商品价格获取方式,只需要修改ProductModule
类实现IProductInfo
接口的getPrice
方法,而不会影响到OrderProcessor
和其他依赖IProductInfo
的模块。如果要添加新的用户信息获取方法,可以在IUserInfo
接口中新增方法,并在UserModule
中实现,OrderProcessor
等依赖IUserInfo
的模块只要不使用新方法就无需修改。在测试OrderProcessor
时,可以方便地创建IProductInfo
和IUserInfo
的Mock对象,独立测试OrderProcessor
的processOrder
方法。