0

我正在开发一个 MVC 项目的新阶段。第一阶段相当小,也不是很复杂。下一阶段将非常复杂且规模更大。在第一阶段,我们讨论了然后出于各种原因放弃了进行彻底自动化测试的想法。

在新阶段,我希望我们的团队投资于自动化测试。我认为如果没有依赖注入,我们将无法做任何有用的事情,因为我们希望将数据库工作存根以使我们的测试可控。

我正在努力将依赖注入改造到我的业务层中。我们现有的业务对象具有状态、行为,并负责加载自身的实例(通过静态方法)。例如,我们可能有这个过于简化的例子:

public class User
{
    public User(string userName)
    {}

    public bool Authenticate()
    {}

    public static User GetByUserName(string userName)
    {
      //Do some DB querying, and then map the data object to a new instance:
      UserRepository repository = new UserRepository();
      var databaseObject = repository.list(u => u.userName = userName);
      User user = new User();
      user.userName = databaseObject.userName;
      // populate other fields
      return user;
    }
 }

乍一看,我似乎需要:

  1. 在构造函数中注入存储库作为依赖项
  2. 将 GetByUserName 更改为不再是 User 中的静态方法
  3. 做某种工厂以便能够在 GetByUserName 方法中创建 User 的实例

我无法弄清楚如何以合理的方式完成所有这些。#1 是微不足道的,但 #2 和 #3 不那么微不足道。这是我正在考虑如何做#2:

  1. 基本上保持结构不变,只需删除 static 关键字。这意味着为了通过用户名获取用户,您必须创建用户对象的实例,然后调用 GetByUserName 方法。这没有通过我高度复杂的气味测试。
  2. 将加载和保存用户的责任转移到某种 UserCollection 对象。这对我来说似乎很可疑,因为这意味着几乎每个业务对象都需要一个底层集合对象,并且因为它会使我的域模型更加贫乏。
  3. 直接与存储库交互。现在控制器可能会调用 User.GetByUserName() 来获取用户域对象来做一些工作。相反,控制器会知道存储库并调用 Repository.list()。这对我来说似乎是泄漏的——突然控制器感觉它在做生意。

然后是#3。看起来我的选择是要么使用我的 IOC 容器来注入工厂方法(例如 autofac 工厂委托),要么为每个对象手动创建工厂。使用 autofac 方法似乎有漏洞,因为我必须在我的单元测试中使用 autofac 或者为每个对象存根一个工厂委托。为所有对象创建工厂似乎很麻烦,因为在某些时候,我给开发人员施加了很大的工作量,只是为了能够在不知道我的 IOC 容器的情况下使用 DI。

我已经尽我所能搜索这些主题,但我真的没有看到很多不涉及上述缺点的具体例子。

有哪些技巧可以解决 #2 和 #3 而不会失去“好的设计”或“做太多的工作”?

4

2 回答 2

1

该类User不应该知道存储库恕我直言。如果您在存储库中连接,则难以对User类进行单元测试,并且如果您决定将存储库放在不同的程序集中,则会创建循环引用。

一个典型的设计是有一个IUserRepository接口来定义你的 CRUD 方法(例如GetUserByUserName)和一个或多个UserRepository指向实际数据源的类。

当您有任何需要使用UserRepository(如控制器)的东西时,将其作为IUserRepository. 这样您就可以使用模拟/存根存储库对控制器进行单元测试。

您的存储库将了解您的业务类,并且可能(松散地)与其他存储库耦合(例如,Role存储库可能会被注入IUserRepository一个角色以获取用户。

如果你想从一个所有类的“主”存储库开始,那很好,但是你冒着以后不得不拆分存储库的风险(这可能不是什么大问题)。

所以我会推荐选项 #2 和子选项 #2(将其称为UserRepositoryvs UserCollection)。

于 2013-02-13T17:43:05.693 回答
0

您如何实现这一点主要取决于您希望将检索数据库对象UserRepository并从中填充User实例的逻辑放置在何处。有些人喜欢拥有可以检索自己数据的“智能”模型,但其他人则不喜欢。

听起来你想要一个有点聪明的User班级。我认为你可以通过将 注入UserRepository到类的构造函数中来实现这一点User,而不是创建另一个 User实例,只需使用存储库来填充这个User实例。

下面是一个示例实现。我的期望是您使用的是 IoC 容器,并且您将在其中注册 IUserRepository 接口。我假设您需要用户存储库的接口,因为您可能想模拟它以进行单元测试等。

public class User
{
    public User(IUserRepository repository, string userName)
    {
      var databaseObject = repository.list(u => u.userName = userName);

      this.userName = databaseObject.userName;
      // populate other fields
    }

    public bool Authenticate()
    {}
 }

现在您应该能够通过 IoC 容器实例化一个用户对象。例如,如果您使用的是 Ninject,那么我认为语法如下:

var user = kernel.Get<User>(new ConstructorArgument("userName", "<actual username value>"));

使用 Unity,我相信它会是这样的:

container.Resolve<User>(new ParameterOverride("userName", "<actual username value>"))

根据您的 IoC 容器,您可能还需要注册 User 类,即使它没有实现接口并且您并没有真正尝试将其换成任何东西。可能需要将您注册的 IUserRepository 与 User 类的构造函数参数连接起来。

如果您真的想保持您的实例化代码干净,那么您可以保留 User 类上的静态方法并让它执行上面的一行。

免责声明:我没有测试过这段代码。

于 2013-02-13T18:48:03.097 回答