【问题标题】:How to decouple implementation details when they depend on data in calling code当它们依赖于调用代码中的数据时如何解耦实现细节
【发布时间】:2018-06-17 19:24:49
【问题描述】:

如果实现效果必须依赖于调用代码中的某些参数,如何将接口实现与调用代码解耦?

这听起来可能令人费解,所以这里是一个真实的例子:

  • 一些网店销售不同品牌的产品;
  • 在处理用户购买时,我们需要根据所购买产品的品牌,将数据提交到不同的存储中;
  • 所有此类存储都可以通过相同接口的相同实现访问,但通过不同的底层连接。

按照 IoC 原则,我们应该有一个存储接口实现的实例,以便在不了解其内部结构的情况下处理代码。但是,数据必须根据产品的品牌发送到不同的服务器,这意味着我们必须以某种方式影响该实现。

如果我们将任何数据(品牌数据或存储库连接配置)传递到存储库,我们要么将存储库接口耦合到品牌实体,要么将处理代码订购到存储库实现细节。

那么如何按照 IoC 原则来实现这个场景呢? 也欢迎提出其他解耦模式的建议。

【问题讨论】:

  • 在许多情况下,存储库接口是通用的。因此,它不会绑定到您的数据接口,反之亦然。然后,您的业务逻辑代码使用数据和存储库接口来执行所需的工作。
  • 通用与否,存储库必须根据调用代码的输入将一些数据保存到不同的存储中。问题是如何在不将调用代码耦合到存储库实现的情况下进行控制。
  • 您可以通过在不同的程序集中分离定义(接口)和实现(类)来做到这一点。您将有一个与存储库相关的接口(例如只读存储库、读/写存储库...)的合同程序集和一个实现程序集。通常,它们构建在不同的解决方案中,以允许在不更改接口程序集版本等的情况下对实现进行错误修复/更改/扩展。要存储的数据也是如此。然后你将存储库和数据接口注入到适当的工作类中,他们不知道他们的实现......
  • 你误解了这个问题。我不是在问 IoC/DI 是如何工作的,而是在这个特定的场景中如何使用它。我自己发布了一个答案,可能会消除混乱。不过还是感谢您的意见!

标签: dependency-injection dependencies inversion-of-control decoupling


【解决方案1】:

我得出的结论是,在这种情况下,代码不能完全解耦。根据手头任务的定义,业务逻辑和存储库实现之间存在耦合。

但是,为了简化进一步的代码维护,我最终使用了以下架构(以伪代码形式):

核心接口:

// 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 仍然与存储库的实现耦合,因为它必须知道如何以及从何处获取它们的配置。

我会将此答案标记为已接受,但如果有人提出更好的想法,我会愿意更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-07
    • 1970-01-01
    • 2015-10-14
    • 1970-01-01
    • 2018-09-08
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多