当实现细节依赖于调用代码中的数据时如何解耦实现细节
How to decouple implementation details when they depend on data in calling code
如果实现效果必须依赖于调用代码中的某些参数,您如何将接口实现与调用代码分离?
听起来可能有些绕口,下面是一个真实的例子:
- 一些网上商店销售不同品牌的产品;
- 处理用户购买时,我们需要根据购买的产品品牌,将数据提交到不同的存储;
- 所有此类存储都可以通过相同接口的相同实现访问,但需要通过不同的底层连接。
遵循 IoC 原则,我们应该在订单处理代码中有一个存储接口实现的实例,而无需了解其内部结构。但是,数据必须根据产品的品牌发送到不同的服务器,这意味着我们必须以某种方式影响该实现。
如果我们将任何数据(品牌数据或存储库连接配置)传递到存储库,我们要么将存储库接口耦合到品牌实体,要么将订单处理代码耦合到存储库实现细节。
那么如何根据 IoC 原则实现这一场景呢?
也欢迎提出其他解耦模式的建议。
我得出的结论是,在这种情况下,代码不能完全解耦。根据手头任务的定义,业务逻辑和存储库实现之间存在耦合。
但是,为了进一步简化代码维护,我最终使用了以下架构(伪代码):
核心接口:
// Main repository interface
interface OrdersRepositoryInterface {
store(order: Order): bool;
// …other methods
}
// An interface of a factory that will be used to create repository instances with different configurations (different underlying storages, for example)
interface OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface;
}
// An interface of a container that will create different instances of repositories based on specified brand
// An implementation of this interface is the coupling point between business logic and data persistence logic
interface OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface;
}
存储库工厂实现(与存储库本身紧密耦合,如果在接口本身中指定存储库构造函数可以避免,但我个人认为这是不好的做法):
class OrdersRepositoryImplementation implements OrdersRepositoryInterface {
constructor(configuration: OrdersRepositoryConfigurationInterface) {
// …
}
store(order: Order): bool {
// …
}
}
class OrdersRepositoryFactory implements OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface {
return new OrdersRepositoryImplementation(configuration);
}
}
存储库容器实现:
class OrdersRepositoryContainer implements OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface {
var factory = IoC.resolve(OrdersRepositoryFactoryInterface);
var configuration1 = …;
var configuration2 = …;
if (brand.slug === "brand1") {
return factory.createRepository(configuration1);
} else {
return factory.createRepository(configuration2);
}
}
}
IoC 容器绑定(如果容器支持它,绑定 类 可能比实例更好,因为在这些实现的构造函数中可能会使用自动依赖注入):
IoC.bindInstance(OrdersRepositoryFactoryInterface, new OrdersRepositoryFactory());
IoC.bindInstance(OrdersRepositoryContainerInterface, new OrdersRepositoryContainer());
最后但同样重要的是,订单处理代码:
var order = …;
var repositoryContainer = IoC.resolve(OrdersRepositoryContainerInterface);
var repository = repositoryContainer.get(order.brand);
repository.store(order);
此架构将允许轻松替换存储库解析逻辑。例如,未来所有品牌的存储库可能会统一,在这种情况下,只需替换 OrderRepositoryContainerInterface 实现。
但是,OrdersRepositoryContainer 仍然与存储库的实现耦合,因为它必须知道如何以及从何处获取配置。
我会将此答案标记为已接受,但如果有人提出更好的想法,我会愿意更改。
如果实现效果必须依赖于调用代码中的某些参数,您如何将接口实现与调用代码分离?
听起来可能有些绕口,下面是一个真实的例子:
- 一些网上商店销售不同品牌的产品;
- 处理用户购买时,我们需要根据购买的产品品牌,将数据提交到不同的存储;
- 所有此类存储都可以通过相同接口的相同实现访问,但需要通过不同的底层连接。
遵循 IoC 原则,我们应该在订单处理代码中有一个存储接口实现的实例,而无需了解其内部结构。但是,数据必须根据产品的品牌发送到不同的服务器,这意味着我们必须以某种方式影响该实现。
如果我们将任何数据(品牌数据或存储库连接配置)传递到存储库,我们要么将存储库接口耦合到品牌实体,要么将订单处理代码耦合到存储库实现细节。
那么如何根据 IoC 原则实现这一场景呢? 也欢迎提出其他解耦模式的建议。
我得出的结论是,在这种情况下,代码不能完全解耦。根据手头任务的定义,业务逻辑和存储库实现之间存在耦合。
但是,为了进一步简化代码维护,我最终使用了以下架构(伪代码):
核心接口:
// Main repository interface
interface OrdersRepositoryInterface {
store(order: Order): bool;
// …other methods
}
// An interface of a factory that will be used to create repository instances with different configurations (different underlying storages, for example)
interface OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface;
}
// An interface of a container that will create different instances of repositories based on specified brand
// An implementation of this interface is the coupling point between business logic and data persistence logic
interface OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface;
}
存储库工厂实现(与存储库本身紧密耦合,如果在接口本身中指定存储库构造函数可以避免,但我个人认为这是不好的做法):
class OrdersRepositoryImplementation implements OrdersRepositoryInterface {
constructor(configuration: OrdersRepositoryConfigurationInterface) {
// …
}
store(order: Order): bool {
// …
}
}
class OrdersRepositoryFactory implements OrdersRepositoryFactoryInterface {
createRepository(configuration: OrdersRepositoryConfigurationInterface): OrdersRepositoryInterface {
return new OrdersRepositoryImplementation(configuration);
}
}
存储库容器实现:
class OrdersRepositoryContainer implements OrdersRepositoryContainerInterface {
get(brand: Brand): OrdersRepositoryInterface {
var factory = IoC.resolve(OrdersRepositoryFactoryInterface);
var configuration1 = …;
var configuration2 = …;
if (brand.slug === "brand1") {
return factory.createRepository(configuration1);
} else {
return factory.createRepository(configuration2);
}
}
}
IoC 容器绑定(如果容器支持它,绑定 类 可能比实例更好,因为在这些实现的构造函数中可能会使用自动依赖注入):
IoC.bindInstance(OrdersRepositoryFactoryInterface, new OrdersRepositoryFactory());
IoC.bindInstance(OrdersRepositoryContainerInterface, new OrdersRepositoryContainer());
最后但同样重要的是,订单处理代码:
var order = …;
var repositoryContainer = IoC.resolve(OrdersRepositoryContainerInterface);
var repository = repositoryContainer.get(order.brand);
repository.store(order);
此架构将允许轻松替换存储库解析逻辑。例如,未来所有品牌的存储库可能会统一,在这种情况下,只需替换 OrderRepositoryContainerInterface 实现。 但是,OrdersRepositoryContainer 仍然与存储库的实现耦合,因为它必须知道如何以及从何处获取配置。
我会将此答案标记为已接受,但如果有人提出更好的想法,我会愿意更改。