依赖关系反转:如何最好地管理抽象的版本控制



在高代码重用环境中应用依赖关系反转时,如何在 .Net 中对抽象进行版本控制

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

假设/限制

预先提出的一个相当大的假设或限制是,我的开发团队坚持将部署的程序集保留为一个且只有一个程序集版本的规则,特别是版本"1.0.0.0"。 到目前为止,为了简单起见,我们不支持在服务器上部署我们开发的任何给定程序集的多个程序集版本。 这可能是限制性的,可能有很多很好的理由来摆脱这一点,但无论如何,这是我们目前使用的规则。 因此,考虑到这种做法,请继续下面的操作。

场景

  • 您有一个抽象程序集中包含的 IDoStuff 接口 Stuff.Abstractions.dll有2种方法。
  • 编译组件 A.dll 使用一个类显式实现 IDoStuff 使用 2 个方法。
  • 将 A.dll移动到生产用途,程序集版本 1.0.0.0,程序集文件 版本 1.0.0.0。
  • 将"接口.dll"移动到"产品"、"程序集版本" 1.0.0.0,程序集文件版本 1.0.0.0。
  • 一切正常。 时光荏苒。
  • 您将另一个方法(例如"DoMoreStuff")添加到 IDoStuff 接口,以便不同的组件 B 可以调用它。 (牢记接口隔离OO原则,假设DoMoreStuff方法在这个相对较小的IDoStuff接口中是有意义的。
  • 您现在在 Stuff.Abstractions.dll 中拥有 3 个方法的 IDoStuff,并且您已经构建了组件 B 以使用新的第 3 种方法。
  • 将 Stuff.Abstractions.dll 移动到生产用途(升级它),程序集版本 1.0.0.0,程序集文件版本 1.0.0.1。 (请注意,文件版本会递增,但程序集版本和因此强名称保持不变)
  • 将 B.dll 移动到生产用途,程序集版本 1.0.0.0,程序集文件版本 1.0.0.17。
  • 你不会对A做任何事情.dll。 您认为此时不需要更改。

现在,您调用尝试在以前工作的同一生产服务器上执行 A.dll 的代码。在运行时,依赖关系反转框架将 IDoStuff 接口解析为 A.dll 中的类并尝试创建它。 问题是 A.dll 中的类实现了现已灭绝的 2 方法 IDoStuff 接口。 正如人们所期望的那样,你会得到一个这样的异常:

程序集"程序集 A.dll 的强名称.dll"类型"A 中的 IDoStuff 类"中的方法"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,那么一切都应该很好。 答.dll也不需要更改。 每当它接下来需要模组时,您可以选择实现存根并升级界面,或者什么都不做。 如果它只需要它们,也许最好将其保留为首先可以访问的 2 种方法。 作为附带的好处,我们知道任何引用 stuff.abstractions.dll 版本 1.0.0.0 只能访问 2 个接口方法,而 1.0.0.1 的用户可以访问 3 个方法。

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

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

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

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

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

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

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

我将该接口注入到我的类中,并继续编写我的类,将编写实现推迟到以后。

然后是时候实现该类了,那时我可以引入我的其他服务,该服务的设计意图不仅仅是为这个类提供服务,并使其适应我的界面。

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存在是因为我定义了它来执行我的类所需的操作,因此我永远不必担心必须实现不需要的接口成员。永远不会有。

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

public interface IDoStuff
{
void GoFirst();
void GoSecond();
}

然后可能会有

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

然后组件 A 可以引用IDoStuff组件 B,并且可以针对IDoStuffV2编写组件 B。 有些人对接口继承不屑一顾,但我看不到任何其他方法可以轻松对接口进行版本控制。

最新更新