依赖倒置:如何最好地管理抽象的版本控制

Dependency Inversion: How to best manage versioning of your Abstractions

在高代码重用环境中应用依赖倒置时如何在 .Net 中对抽象进行版本控制

我有兴趣转向在 .Net 中使用依赖倒置,但遇到了一些让我困惑的事情。 我认为它与 DIP 的特定方法或提供者无关,但更多的是其他人可能已经解决的基本问题。我要解决的问题最好按以下方案逐步说明。

假设/限制

首先要提出的一个相当大的假设或限制是,我的开发团队一直坚持将我们部署的程序集保持在一个且只有一个程序集版本的规则,特别是版本“1.0.0.0”。 到目前为止,为了简单起见,我们不支持在服务器上部署我们开发的任何给定程序集的多个版本。这可能是有限的,并且可能有很多很好的理由来摆脱它,但无论如何,这是我们目前使用的规则。所以记住这个练习,继续下面。

场景

现在您调用尝试在它之前工作的同一生产服务器上执行 A.dll 的代码。在运行时,依赖倒置框架将 IDoStuff 接口解析为 A.dll 内的 class 并尝试创建它。 问题是 A.dll 中的 class 实现了现在 extinct 2 方法 IDoStuff 接口。正如人们所预料的那样,您会遇到这样的异常:

程序集“强名称程序集A.dll”中类型“A.dll”中的“IDoStuff Class”中的方法“DoMoreStuff”确实没有实现。

当我必须向现有接口添加方法时,我可以想到两种方法来处理这种情况:

1) 更新每个使用 Stuff.Abstractions.dll 以实现新“DoMoreStuff”方法的功能提供程序集。 这似乎是在以艰难的方式做事,但以蛮力的方式会很痛苦。

2) 改变上述假设/限制并开始允许存在多个程序集版本(至少对于抽象定义程序集而言)。 这会有点不同,并且会在我们的服务器上创建更多的程序集,但它应该允许以下结束状态:

A.dll 取决于 stuff.abstractions.dll,程序集版本 1.0.0.0,程序集文件版本 1.0.0.22(AFV 除了识别构建外无关紧要) B.dll 取决于 stuff.abstractions.dll,程序集版本 1.0.0.1,程序集文件版本 1.0.0.23(AFV 除了识别内部版本外无关紧要) 两者都能愉快地在同一台服务器上执行。

如果 stuff.abstractions.dll 的两个版本都安装在服务器上,那么一切都会正常进行。 A.dll 也不需要更改。每当它接下来需要 mod 时,您都可以选择实现存根并升级接口,或者什么都不做。如果它只需要它们,也许最好将它减少到它首先可以访问的 2 种方法。 作为一个附带的好处,我们知道任何引用 stuff.abstractions.dll,版本 1.0.0.0 只能访问 2 个接口方法,而 1.0.0.1 的用户可以访问 3 个方法。

是否有更好的方法或可接受的版本控制抽象部署模式?

如果您尝试在 .Net 中实现依赖倒置方案,是否有更好的方法来处理版本控制抽象? 如果你有一个单一的应用程序,它看起来很简单,因为它全部包含在内——只需更新界面用户和实现者。 我试图解决的特定场景是一个高代码重用环境,其中有很多组件依赖于很多组件。依赖倒置确实有助于分解并使单元测试感觉不像系统测试(由于紧密耦合层)。

部分问题可能是您直接依赖于为更广泛的目的而设计的界面。您可以通过让 classes 依赖于为它们创建的抽象来缓解这个问题。

如果您根据需要定义接口来表示您的 classes 的依赖关系而不是依赖于外部接口,您将永远不必担心实现不需要的接口成员。

假设我正在写一个涉及订单发货的 class,我意识到我需要验证地址。我可能有执行此类验证的库或服务。但我不一定想将该接口直接注入我的 class,因为现在我的 class 具有面向外的依赖性。如果该接口增长,我可能会因依赖于我不使用的接口而违反接口隔离原则。

相反,我可能会停下来写一个接口:

public interface IAddressValidator
{
    ValidationResult ValidateAddress(Address address);
}

我将该接口注入我的 class 并继续编写我的 class,推迟编写实现。

然后是实施该 class 的时候了,那时我可以引入我的其他服务,该服务的设计意图比仅仅为这个 class 提供服务更广泛,并对其进行调整到我的界面。

public class MyOtherServiceAddressValidator : IAddressValidator
{
    private readonly IOtherServiceInterface _otherService;

    public MyOtherServiceAddressValidator(IOtherServiceInterface otherService)
    {
         _otherService = otherService;
    }

    public ValidationResult ValidateAddress(Address address)
    {
        // adapt my address to whatever input the other service
        // requires, and adapt the response to whatever I want
        // to return.
    }
}

IAddressValidator 存在是因为我将其定义为执行我的 class 所需的操作,因此我永远不必担心必须实现我不需要的接口成员。永远不会有。

始终可以选择对接口进行版本控制;例如,如果有

public interface IDoStuff
{
    void GoFirst();

    void GoSecond();
}

然后会有

public interface IDoStuffV2 : IDoStuff
{
    void GoThird();
}

那么ComponentA可以引用IDoStuff,ComponentB可以写成IDoStuffV2。有些人不赞成接口继承,但我没有看到任何其他方法可以轻松地对接口进行版本控制。