关于DI和SRP

时间:2013-03-13 01:45:15

标签: c# dependency-injection domain-driven-design single-responsibility-principle

我正在编写以下课程

public class UserApplication
{
    private IUserRepository UserRepository { get; set; }

    private IUserEmailerService UserEmailerService { get; set; }

    public UserApplication(IUserRepository userRepository, IUserEmailerService userEmailerService)
    {
        this.UserRepository = userRepository;
        this.UserEmailerService = userEmailerService;
    }

    public bool Authenticate(string login, string pass)
    {
        // Here I use UserRepository Dependency
    }

    public bool ResetPassword(string login, string email)
    { 
        // Here I only use both Dependecies
    }

    public string GetRemeberText(string login, string email)
    {
        // Here I only use UserRepository Dependency
    }
}

我正在使用Unity来管理我的实例,所以我意识到我只在一个方法上使用这两个依赖项,所以当我要求容器为这个类提供一个实例时,两个依赖项都注入到这个类中但是我没有需要所有方法的两个实例,因此在Authenticate用户中我只需要存储库。 我这样做错了吗?还有另一种方法,只有我在本课程中用于所有案例的依赖吗?

我想使用命令模式,所以我用一个方法来分类3个类,只有我需要的依赖项,如下所示:

public class AuthenticateUserCommand : ICommand
{
    private IUserRepository UserRepository { get; set; }

    public string Login { get; set; }

    public string Password { get; set; }

    public void Execute()
    {
        // executes the steps to do that
    }
}



public class ResetUserPasswordCommand : ICommand
{
    private IUserRepository UserRepository { get; set; }

    private IUserEmailerService UserEmailerService { get; set; }

    public string Login { get; set; }

    public string Email { get; set; }

    public void Execute()
    {
        // executes the steps to do that
    }
}

3 个答案:

答案 0 :(得分:0)

另一种方法是为每个行为创建特定于角色的界面。因此,您有IUserAuthenticationServiceIUserPasswordResetServiceIUserRememberPasswordService。接口可以由单个类实现,例如UserApplication,也可以使用单个类来实现,以维护SRP。您描述的命令模式对SRP具有类似的优势。命令模式的一个问题是这些依赖关系仍然必须由某些东西提供。如果依赖项是由控制器提供的,那么您仍然必须首先将依赖项提供给控制器,并且您将遇到与第一个示例类似的问题。

角色特定界面案例以及命令模式的权衡是内聚力的丧失。这样做的成本肯定是偏好和视角的问题,以及您希望强制执行SRP的程度。一方面,通过使用单个类处理认证相关行为而提供的内聚可能是有益的。另一方面,它可能导致依赖性错位,如您所述。

答案 1 :(得分:0)

我通常会在你考虑的时候实现一种命令模式。但是,它也有eulerfx提到的元素。我只是称他们为任务。例如:

public interface ITask
{
    void Execute();
}

public interface IAuthenticateTask : ITask {}

public interface IResetPasswordTask : ITask {}

然后我实现了这些并注入了所需的依赖项。所以我有角色特定的接口和实现。

我会按照您在答案位中的说明使用服务定位器。

当我确实需要像在ASP.NET MVC项目的控制器中那样访问各种任务时,我使用属性注入而不是构造函数注入。我只是让我的DI容器(我使用城堡)需要在运行时基于约定进行某些注射。

通过这种方式,我仍然可以轻松地测试控制器,因为我不需要提供所有构造函数注入的对象,而只需要提供测试所需的那些属性,还有一些额外的好处,即仍然需要某些注入的属性。将由构造函数注入提供。

<强>更新

使用这种方法有几种选择。人们对任务感兴趣的主要界面是角色特定的。诸如ITask之类的继承接口仅仅是为了方便起见。它也可以用泛型扩展:

public interface ITask<TInput>
{
   void Execute(TInput input);
}

public interface IOutputTask<TOutput>
{
   TOutput Execute();
}

public interface IOutputTask<TOutput, TInput>
{
   TOutput Execute(TInput input);
}

这些都是为了方便起见:

public interface IAuthenticateTask : IOutputTask<bool> {}

// or

public interface IAuthenticateTask : IOutputTask<AuthenticationResult> {}

您可以在角色级别上工作:

public interface IAuthenticateTask
{
    AuthenticationResult Execute(string username, string password);
}

只需依赖特定于角色的依赖关系接口。

答案 2 :(得分:0)

  

我这样做错了吗?

没有。 2个依赖项不是世界末日,它们不会使构造函数变得臃肿或不可读。

以前给出的答案很适合你有3-4 +依赖的情况。

如果仅在该方法中使用,您还可以偶尔将特定依赖项作为参数传递给方法。从这个意义上说,你可能想给Unity method call injection一个尝试,虽然我不确定它是否真正用于此目的。