除了实例化 DAL 的 BLL 之外,还有什么选项允许在 n 层解决方案中进行单元测试,而无需将 DAL 暴露给 UI 或将 BLL 暴露给 DAL?

What option other than a BLL instantiating a DAL allows for unit testing in an n-tier solution without exposing DAL to the UI or BLL to DAL?

我有一个分层解决方案如下:

我希望 BLL 有一个名为 GetProductList() 的服务,该服务在我的 DAL 层中实现。我想过在BLL和DAL实现中定义一个接口如下:

选项A:

// Interface defined in BLL 
public interface IDataServices
{
  List<Product> GetProductList();
}

// Interface implemented in DAL
public class DataServices : IDataServices
{
    Public List<Product> GetProductList()
    {
      return //do some database work here and return List<Product>;
    }
}

如果我想在 DAL 中实现它,那么我必须让 DAL 项目引用 BLL 项目才能看到 IDataServices 的接口定义。或者,我可以在 DAL 中复制接口定义,但我最终会得到要维护的重复代码(BLL 和 DAL 中的相同接口定义)。

选项B: 我可以做到这一点的另一种方法是忘记接口的想法,只是在 UI 可以使用的 BLL 中进行以下具体 class 和方法调用:

// Concrete class defined in the BLL
public class DataServices
{
    Public List<Product> GetProductList()
    {
         DAL aDAL = new DAL();
         Return (aDAL.GetProductList());
    }
}

这很简单,但是 BLL 会看到 DAL 并引用它,但这真的是一件坏事吗?只要 BLL 不使用任何数据库对象(即数据源、连接字符串等)来满足请求并且 DAL 符合匹配我在 BLL DataServices class 中定义的服务名称足够了?我听到的关于在另一个数据库引擎中交换的所有讨论仍然可以通过确保下一个 DAL 提供 BLL 在 DataServices class 中标识的相同服务(例如 GetProductList())来完成。在此设置中,UI 仍然不知道有关 DAL 的任何信息,而 DAL 也不知道有关 BLL 的任何信息。如果我接受使用依赖注入来避免在 BLL 中实例化 DAL 的想法,这将意味着在 UI 中实例化它以传递到 BLL 中。我不想这样做,因为它会给 UI 访问 DAL 方法的权限。

选项C: 我快速浏览了 Unity Container,但该工具建议在入口点预先注册所有接口和具体的 class,这本来是 UI,而后者最终给出了 UI BLL 和 DAL 的可见性似乎更差。我看到了将 MEF 与 Unity 结合使用来解决入口点看到所有层的问题的参考,但我也看到如果你这样做,你就不能真正地对这样的配置进行单元测试。与选项 B 相比,工作量大且复杂。

选项D: 我考虑过的另一个选择是在 BLL 和 DAL 之间创建一个外观层(另一个 VS 项目)。这似乎没有多大意义,除非我最终得到了很多与 BLL 无关的 DAL 方法,因此必须隐藏它们;允许 DAL Facade 仅显示 BLL 需要的内容。如果我要交换到另一个数据库中,我仍然需要根据 BLL 的需要创建外观所需的方法,正如我在选项 B 中提到的那样。

因此,基于所有这些,我正在考虑选择选项 B,但我想在这里提供一些社区意见。满足以下条件还能做什么:

IDataServices 应该在 DAL 中定义。 but then the BLL sees the DAL and has a reference to it 这是很自然的做法。一个项目可以引用它下面的层。如果你不允许向下引用,那么根本就没有引用。

请注意,反射引用仍然是一个引用,因为您不能在不更改上面的图层的情况下更改下面的图层。依赖关系不是仅限编译时的概念。选项 B 没有增加任何好处。它删除了编译时依赖性但不删除运行时依赖性。

移除 A 到 B 的依赖关系的要点在于,您可以在不更改 A 的情况下更改 B。这就是全部要点。运行时依赖性仍然很重要。

关于 C:您可以让 UI 要求 BLL 注册其依赖项。然后 BLL 可以要求 DAL 注册。这样 UI 就可以免受 DAL 的影响。

D:我不知道这会完成什么。

通过使 DAL 定义 IDataServices.

可以轻松满足您的约束

你的选项 A(BLL 中的接口,DAL 中的实现)+ IoC 容器是最好的方法。

在设计复杂的软件解决方案时,您必须将其分成更小的部分。

其中一些部分对于解决方案至关重要。它们将是您开发软件的原因,而不仅仅是购买现有解决方案。你必须专注于他们。这些部分将很复杂且难以实施。你对此无能为力。它们必须尽可能得到最好的实施。

也会有简单的作品。易于实现的功能,甚至可以购买。尝试以尽可能少的努力制作这些零件。它们是必要的,但它们不是您受雇的目的。

首先关注困难的部分。那里的业务逻辑会很复杂。业务逻辑将不依赖于您选择的存储解决方案——所以不要让它依赖于您系统中的 DAL 项目。在您的业务逻辑层中为您的实体(如果您遵循 DDD,则为聚合)的存储库定义接口。

您应该能够彻底测试业务逻辑。如果 BLL 依赖于 DAL,则无法执行此操作。

UI应该和业务逻辑完全分离。 UI 只是用户可以执行的一组用例。因此,它应该向用户提供有限的信息,并且只接受来自用户的有限信息。

要将 UI 与 BLL 分开,请使用单独的 ViewModel 和命令 类 来显示和接受来自用户的信息。在每个用例中使用额外的应用层来编排 BLL。

这种方法众所周知 Hexagonal architecture, Onion architecture or Clean architecture。它也在领域驱动设计书籍中被引用。

当然,您需要一个地方将所有这些依赖项放在一起。这个地方是一个composition root,应该尽可能靠近应用入口点。如果您不想引用 UI 项目中的所有层,请将合成根移动到另一个项目。