C# 关于DI和SRP

C# 关于DI和SRP,c#,dependency-injection,domain-driven-design,single-responsibility-principle,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 userEma

我正在写下一节课

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 user中,我只需要存储库。 那么我这样做错了吗?是否有另一种方法只具有我在这个类的所有情况下使用的依赖性

我考虑使用命令模式来实现这一点,因此我用一个方法对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
    }
}

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


特定于角色的接口案例和命令模式之间的权衡是内聚性的丧失。这当然是一个偏好和视角的问题,也是您希望在多大程度上实施SRP的问题。一方面,由单个类处理与身份验证相关的行为所提供的内聚是有益的。另一方面,正如您所描述的,它可能会导致依赖关系不一致。

我通常在您考虑这样做时实现一种形式的命令模式。然而,它也包含了eulerfx提到的元素。我把它们叫做任务。例如:

public interface ITask
{
    void Execute();
}

public interface IAuthenticateTask : ITask {}

public interface IResetPasswordTask : ITask {}
然后我实现了这些,并注入了所需的依赖项。所以我有特定于角色的接口和实现

我不会像你在回答中所说的那样使用服务定位器

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

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

更新:

使用这种方法有两种选择。任务的主界面是特定于角色的。继承的接口(如
ITask
)仅用于简单情况下的方便。它还可以通过泛型进行扩展:

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

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

public interface IOutputTask<TOutput, TInput>
{
   TOutput Execute(TInput input);
}
依赖关系只需要依赖于特定于角色的接口

那么我这样做错了吗

第二,依赖关系不是世界末日,它们不会使构造函数膨胀或无法读取

之前给出的答案非常适合于3-4+依赖的情况


如果某个特定依赖项仅在某个方法中使用,则偶尔也可以将该依赖项作为参数传递给该方法。从这个意义上讲,您可能想尝试一下Unity,尽管我不确定它是否正是为了达到这个目的。

对于ASP.NET MVC,由于控制器的性质,我可以看到第一个示例更可取,而后一个示例可以与WPF MVVM配合使用,因为您可以直接绑定命令。它实际上只是取决于您使用的技术。不,这个类不是我系统的ASPNET MVC这一层的一部分。当一个类被注入了一个依赖项,而该依赖项不是该类主要使用的,这可能意味着该类可能正在做它不负责的事情,从而打破了SRP原则,这可能不是您的情况,我只是建议。好的,但如果我为每个行为创建一个接口,并在UserApplication类上实现这些接口,那么您同意我的观点,即当我要求container为我提供这个类的实例时,其他两个依赖项将注入UserApplication,所以问题仍然存在。那么让容器给我一个实例来说明每个方法的依赖性如何?我真的很喜欢每个行为都有接口,并在一个类上实现它,但我不太愿意这样做,因为我的系统会有很多类和一个方法。是的,如果UserApplication实现了所有接口,您就回到了开始的地方。是的,使用特定于角色的接口方法,最终会得到很多类。这是一种权衡。你要么有很多小的、专用的类,要么有更大的、可能合并的类。这些都是函数式编程解决的OOP陷阱的一部分。谢谢!关于ideia,这个简单的类使用Execute方法,然后使用UserApplication来处理?像这个公共bool Authenticate(iauthencateesertask){task.Execute();}我已经更新了答案,以说明仅依赖(应该)角色特定接口时的一些选项。
public interface IAuthenticateTask : IOutputTask<bool> {}

// or

public interface IAuthenticateTask : IOutputTask<AuthenticationResult> {}
public interface IAuthenticateTask
{
    AuthenticationResult Execute(string username, string password);
}