【问题标题】:Dependency Injection across assemblies & namespaces跨程序集和命名空间的依赖注入
【发布时间】:2023-03-25 01:01:01
【问题描述】:

我正在处理一个我认为我理解其原因的 DI 问题,但我需要一些建议来解决。

我已经构建了一个与 Sql 对话的独立程序集(称为此程序集 a),以及另一个包含业务逻辑的程序集(称为此程序集 b)。我在 b 程序集中为 db 类创建了一个接口。由于接口不是 db 程序集的一部分,因此我不需要对 db 项目的任何引用,如果我想在运行时运行单元测试而不是程序集,我可以加载对 db 程序集或存根的引用需要了解对方。

我可以在编译的业务逻辑库中编写如下代码:(假设 a 和 b 是它们各自程序集中的命名空间)

 a.IDatabaseClass db_class = (a.IDatabase)new b.Database();

但是,当我尝试运行它时,我得到一个无效的强制转换异常。我认为它可以编译,因为接口与类完美匹配,但在运行时失败,因为对象签名在 Database 类的继承链中看不到 IDatabase。

在 c++ 中,您可以随意转换任何东西,但 c# 在转换对象指针方面要严格一些。即使该类具有所有正确的函数签名,它还是因为对象不匹配而崩溃。

现在我可以将 db 对象接口与 db 对象放在程序集中,但是业务逻辑需要对 db 程序集的引用。此外,这只会在未来产生复杂性,因为如果我在单元测试中编写存根 db 对象,我需要一个对 db 程序集的引用,只是为了我将在我的测试存根对象中使用的接口。这样做似乎并没有解开耦合...

我可以将所有接口放在作为 db 程序集、业务逻辑和单元测试的父级的第三个程序集中。这就是解决循环依赖问题的方法。但是,这会将 db 程序集与父程序集联系在一起,并使其与其他项目一起使用的模块化程度大大降低。

我愿意接受有关如何设置每个程序集以便它们独立运行并可用于 DI 的建议。我想我可以将测试存根对象保存在与真实代码相同的程序集中,但这似乎很奇怪。

解决方案:下面的回复之一评论说,我所拍摄的基本上是界面的鸭式打字。 C# 目前不支持鸭子类型,但我认为这可能是可能的,因为接口实现的行为方式类似于您可能称之为部分类指针(或更准确地说,可能是函数指针的集合)。我的实验告诉我不是这样,这就是原因。

所以在 Redmond 将“more mallard”放入 c# 之前,看起来我们无法完全达到最终优雅的解耦程序集水平。

【问题讨论】:

  • 能否确认sql库引用的是业务逻辑库?那b.Database 实现a.IDatabase?或者,这些库是严格独立的吗?
  • 再次阅读帖子后发现它们是完全独立的库。我肯定会选择“第三图书馆”的方法。它绝对是最干净的,我不同意仅仅引用一个暴露接口的库会创建你的 sql 库的紧密耦合。
  • 你是对的,它们都是独立的(或可能是)相互独立的。我希望能够使用 DI 将任何一个程序集与另一个程序集完全断开,并且 exe 或单元测试将在运行时传输数据。似乎 c# 试图通过强类型来使我的代码更安全,这阻碍了我实现这一理想。

标签: c# unit-testing dependency-injection


【解决方案1】:

创建一个包含常用接口的参考库。这样一来,您将拥有所有与实现无关的逻辑的共同来源。

我没有经验有信心将其作为一个明确的陈述,但我强烈怀疑,当您谈论引用特定接口的单个​​类型及其行为方式时,耦合主要是一个问题。程序集没有那种会导致耦合问题的特殊性。

编辑 1

让我修改和扩展。一个程序集必须引用另一个程序集,或者两者都必须引用一个公共程序集。如果有的话,C# 没有鸭子接口。

现在我认为最好的做法是将业务逻辑与外部接口隔离,因此,如果您要执行任何操作,您应该将业务逻辑与数据库实现隔离。所以 DB/Application 程序集引用业务逻辑,而不是相反。

Here's more information about the dependency orientation.

【讨论】:

【解决方案2】:

让客户端库 (b) 定义接口,并从服务器库 (a) 中引用该库。

Dependency Inversion Principle 可以看出,“客户端 [...] 拥有抽象接口”(APPP,第 11 章),因此没有理由将接口放在第三个库中。

【讨论】:

  • 这正是我想要做的。我试图变得聪明并做到这一点,而不需要任何一个程序集依赖于另一个程序集。我怀疑不能以这种方式在 c# 中完成。
  • @MadTigger 不,您不能在 C# 中执行此操作。我认为你也不能用 Java 做到这一点。
  • 有理由把接口放到另外一个第三个库里:如果是多对多关系:Alice做一个游戏Alice.Invaders(客户端),把一个“in-app”购买商店插件”Alice.Shop(实现)。该商店应该可以被其他商店替换(例如插件 Bob.Shop)。这些商店仍然应该可以插入其他游戏。 Charlie.Pacman 引用 Alice.Invaders 中的接口毫无意义,因此需要第三个命名空间 lis Alice.Shop.Interfaces。
  • 看看@MaDeRkAn 回答了我什么:stackoverflow.com/questions/35688838/… - 我也有类似的问题。
  • 实际上从您引用的文章中,它明确地说:“尽管如此,“反转”概念并不意味着较低级别的层依赖于较高级别的层。两个层都应该依赖于绘制的抽象更高级别层所需的行为。” - 所以是的,第三个带有接口的命名空间。
猜你喜欢
  • 2019-04-26
  • 1970-01-01
  • 2016-04-28
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 2012-08-27
  • 2010-10-14
  • 2012-07-06
相关资源
最近更新 更多