【问题标题】:About DI and SRP关于 DI 和 SRP
【发布时间】:2013-03-13 01:45:15
【问题描述】:

我正在写下面的课

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 来管理我的实例,所以我意识到我只在一个方法上使用两个依赖项,所以当我要求容器为这个类提供一个实例时,两个依赖项都注入到这个类中,但我没有所有方法都需要两个实例,因此在验证用户中我只需要存储库。 那我这样做错了吗?是否有另一种方法仅具有我在此类中的所有情况下使用的依赖关系?

我想使用命令模式,所以我用一种方法对 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
    }
}

【问题讨论】:

  • 对于 ASP.NET MVC,由于控制器的性质,我可以看到第一个示例更可取,而后一个示例可以很好地与 WPF MVVM 配合使用,因为您可以直接绑定命令。这实际上取决于您使用的是什么技术。
  • 没有这个类不是 ASPNET MVC 的一部分,这是我系统的一层。
  • 当一个类被注入了一个不是该类主要使用的依赖项时,这可能意味着该类可能正在做它不负责的事情,从而违反了可能不是的 SRP 原则你的情况,我只是建议。

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


【解决方案1】:

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

角色特定的接口案例和命令模式的权衡是失去凝聚力。这样做的成本当然是一个偏好和观点的问题,就像您想要执行 SRP 的程度一样。一方面,由单个类处理身份验证相关行为所提供的凝聚力可能是有益的。另一方面,它可能会导致您所描述的依赖关系错位。

【讨论】:

  • 好的,但是如果我为每个行为创建一个接口并在 UserApplication 类上实现接口,你同意我的观点,当我要求容器给我一个此类的实例时,其他 2 个依赖项是注入 UserApplication 所以问题仍然存在。让容器给我一个实例来说明每个方法的依赖如何?
  • 我真的更喜欢为每种行为提供接口并在单个类上实现它,但我不太愿意这样做,因为我的系统将有很多具有单一方法的类。跨度>
  • 是的,如果 UserApplication 实现了所有接口,你就回到了你开始的地方。是的,使用特定于角色的接口方法,您最终会得到很多类。这是权衡。你要么有很多小的、专门的课程,要么有更大的、可能的合并课程。这些是函数式编程解决的 OOP 缺陷的一部分。
【解决方案2】:

我通常按照您正在考虑的方式实现命令模式的一种形式。但是,它也有 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);
}

只需要依赖角色特定的接口来实现依赖。

【讨论】:

  • 谢谢你!而关于ideia这个简单的类用Execute方法然后用UserApplication来处理呢?像这样 public bool Authenticate(IAuthencateUserTask task) { task.Execute(); }
  • 我已经更新了答案,以说明在仅依赖角色特定界面时(应该如此)的一些选项。
【解决方案3】:

那么我这样做有错吗?

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

之前给出的答案非常适合您有 3-4 个以上依赖项的情况。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 2023-03-16
    • 1970-01-01
    相关资源
    最近更新 更多