单一责任原则问题(我是否正在考虑正确重构)

时间:2017-03-15 21:59:49

标签: c# oop single-responsibility-principle

我目前的班级PropertyManager如下所示:

public class PropertyManager : IDisposable
{
    private readonly IPropertyRepo _propertyRepo;
    private readonly IUserTypeRepo _userTypeRepo;

    public PropertyManager(IPropertyRepo propertyRepo, IUserTypeRepo userTypeRepo = null)
    {
        if (propertyRepo == null)
            throw new ArgumentNullException("propertyRepo");

        _propertyRepo = propertyRepo;

        if (userTypeRepo != null)
            _userTypeRepo = userTypeRepo;
    }
}

我的物业经理将在某种方法中使用_userTypeRepo来完成某项任务。我想我想要制定一条规则,说明每个经理(服务,工厂等)应该对自己的存储库负责。"

这个想法:

PropertyManager,因为它需要对UserTypeRepo执行某些操作,我应该使用UserManager进行此类活动。

因此,这意味着在创建UserManager的实例(即var usrMgr = new UserManager(); // no repo)时,我不会提供回购。相反,UserManager将使用默认构造函数,该构造函数将创建IUserTypeRepo的新实例并提供UserManager的新实例,然后它可以完成其工作。

我认为这完成了一些设计原则,例如分离关注和单一责任,但随后我可能会脱离我的依赖注入设计模式,因为新的Managers现在有多个构造函数,看起来像这样:

public class PropertyManager : IDisposable
{
    private readonly IPropertyRepo _propertyRepo;

    public PropertyManager(){
        // use the default repo
        _propertyRepo = new PropertyRepo();
    }

    // Used from Controller or Unit Testing
    public PropertyManager(IPropertyRepo propertyRepo)
    {
        if (propertyRepo == null)
            throw new ArgumentNullException("propertyRepo");
    }
}

public class UserManager : IDisposable
{
    private readonly IUserRepo _userRepo;

    public UserManager(){
        // use the default repo
        _userRepo = new UserRepo();
    }

    // Used from Controller or Unit Testing
    public UserManager(IUserRepo userRepo)
    {
        if (userRepo == null)
            throw new ArgumentNullException("userRepo");
    }
}

这会不受欢迎吗?还是我走在正确的轨道上?在任何一种情况下,为什么并感谢?

更新。在阅读Yawar的帖子后,我决定更新我的帖子,我认为我有一个相关的问题。

让我们想一想上面的现实世界的例子。我在现实生活中有PropertyManager名为" Robert"他每天早上在工作中执行的工作之一是Open() Property(即,他解锁Property他是Manager。我还有UserManger人管理访问Property的人,她的名字是#34; Sarah"她有一个她称之为EnterProperty()的功能(这是她早上在走进大楼时所做的事情。)

规则: UserManager在使用PropertyManager

时依赖于EnterProperty()

根据所有可接受的标准,这看起来像这样:

物业经理

class PropertyManager : IPropertyManager
{
    private readonly IPropertyRepo _propertyRepo;

    public PropertyManager(IPropertyRepo propertyRepo)
    {
        if (propertyRepo == null)
                throw new ArgumentNullException("propertyRepo");
            this._propertyRepo = propertyRepo;
    }

    // this is when Robert opens the property in the morning
    public void Open()
    {
        _propertyRepo.Open();
    }

    // this is when Robert closes the property in the evening
    public void Close()
    {
        _propertyRepo.Close();
    }

    // this answers the question
    public bool IsOpen()
    {
        return _propertyRepo.IsOpen();
    }
}

用户经理

class UserManager : IUserManager
{
    private readonly IPropertyRepo _propertyRepo;
    private readonly IUserRepo _userRepo;

    public UserManager(IUserRepo userRepo, IPropertyRepo propertyRepo = null)
    {
        if (userRepo == null)
                throw new ArgumentNullException("userRepo");
            this._userRepo = userRepo;

        if (propertyRepo != null)
            this._propertyRepo = propertyRepo;
    }


    // this allows Sarah to physically enter the building
    public void EnterProperty()
    {
        if(_propertyRepo.IsOpen())
        {
            Console.WriteLine("I'm in the building.");
        }else{
            _propertyRepo.Open(); // here is my issue (explain below)
            Console.WriteLine("Even though I had to execute the Open() operation, I'm in the building. Hmm...");
        }
    }
}

Web API控制器

{
    public void OpenForBusiness(){
        private const IPropertyRepo propertyRepo = new PropertyRepo();
        private IPropertyManager propertyManager = new PropertyManager(propertyRepo);
        private IUserManager userManager = new UserManager(new UserRepo(), propertyRepo);

        // Robert, the `PropertyManager`, opens the `Property` in the morning
        propertyManager.Open();

        // Sarah, the `UserManager`, goes into `Property` after it is opened
        userManager.EnterProperty();
    }
}

现在,一切都很酷,我可以走开,我现在有一个使用依赖注入的存储库模式,它支持TDD而不是紧密耦合的类以及其他好处。

然而,真的是真实的吗? (解释为什么我要问第二个)

我认为更现实(现实)的做法是:

Web API控制器

public void Method1()
{
    private IPropertyManager propMgr = new PropertyManager(new PropertyRepo());
    private IUserManager userMgr = new UserManager(new UserRepo()); // no dependencies on any repository but my own

    // 1. Robert, the `PropertyManager`, opens the `Property`
    propMgr.Open();

    // 2. Check to see if `Property` is open before entering
    // choice a. try to open the door of the `Property`
    // choice b. call or text Robert, the `PropertyManager`, and ask him if he opened the `Property` yet, so...
    if(propMgr.IsOpen()){
        // 3. Sarah, the `UserManager`, arrives at work and enters the `Property`
        userMgr.EnterProperty();
    }else{
        // sol, that sucks, I can't enter the `Property` until the authorized person - Robert - the `PropertyManager` opens it
        // right???
    }
}

EnterProperty()上的UserManager方法现在看起来像这样:     //这允许Sarah进入建筑物     public void EnterProperty()     {         Console.WriteLine("我在建筑物里。");     }

上面承诺的解释:

如果我们用现实世界的术语来思考我们必须同意,后者比前者更受欢迎。在考虑存储库时,可以说这是自我的定义(即,一个人)(即,UserRepo具有与User相关的所有数据,是{ {1}}因为DNA,心跳,脑波模式等是人类(UserManager)。因此,允许HumanRepo知道UserManager 有权访问其PropertyRepo方法违反所有真实世界安全原则和业务规则。实际上,这表明通过My Contructor()我可以获得一个接口表示我可以以任何我认为合适的方式使用的Open()。这与PropertyRepo的以下逻辑同义:

我,莎拉 - 一个HumanRepo - 通过我自己的新实例,UserManager通过我的构造函数()创建了罗伯特的全息图界面,PropertyRepo我可以使用我认为合适的任何方式。现在告诉我,我只想使用PropertyManager的{​​{1}}方法,如果罗伯特尚未履行职责,我实际上会使用IsOpen()方法自行完成。 这对我来说是一个安全问题。在现实世界中,这表示我不必等待罗伯特打开PropertyRepo并使用他的Holocopy并实施他的Open()获取访问权限的方法。

这看起来不对。

我认为在上一次实现中,我获得了SoC,SRP,DI,存储库模式,TDD和逻辑安全性,并且尽可能接近实际实现。

你们都在想什么?

3 个答案:

答案 0 :(得分:2)

我认为我同意您的 SoC 并将PropertyManager类分为PropertyManagerUserManager类。你快到了。

我会重构如下所示:

public class PropertyManager : IDisposable, IPropertyManager
    {
        private readonly IPropertyRepo _propertyRepo;


        // Used from Controller or Unit Testing
        public PropertyManager(IPropertyRepo propertyRepo)
        {
            if (propertyRepo == null)
                throw new ArgumentNullException("propertyRepo");
            this._propertyRepo = propertyRepo;
        }
    }


public class UserManager : IDisposable, IUserManager
    {
        private readonly IUserRepo _userRepo;


        // Used from Controller or Unit Testing
        public UserManager(IUserRepo userRepo)
        {
            if (userRepo == null)
                throw new ArgumentNullException("userRepo");
            this._userRepo = userRepo;
        }
    }

注意:只需提取IPropertyManager& IUserManager以便调用类将依赖于接口并提供实现。

如果您希望(您应该)强制客户端提供IPropertyRepoIUserRepo接口的具体实现,那么创建无参数构造函数是没用的。

public PropertyManager(){
        // use the default repo
        _propertyRepo = new PropertyRepo();
    }

我认为你不需要

if (propertyRepo == null)
    throw new ArgumentNullException("propertyRepo");

if (userRepo == null)
    throw new ArgumentNullException("userRepo");

因为IPropertyRepo和IUserRepo将在应用程序启动时通过IoC解析(比如它的MVC,然后在调用控制器之前IoC将解析它们),因此无需检查null。我从未在代码中检查过null的依赖项。

你发布的内容几乎就是这样。

Unit of Work pattern用于不在管理器层中的存储库层。我会从标题中删除它。

希望这有帮助!

答案 1 :(得分:2)

  

我认为这实现了一些OOP目标,例如Separating Concerns   和单一责任原则。

结果相反。现在, PropertyManager 紧密耦合 PropertyRepo ;以前,他们松散耦合。

第一种方法比后者更好。但是, PropertyManager UserManager 不应创建他们依赖的其他对象来完成工作。创建和管理对象的责任应该卸载到 IoC容器

接口 描述了可以做什么,而类描述了它是如何完成的。只有类涉及实现细节 - 接口完全不知道如何完成某些事情。因为只有类具有构造函数,所以构造函数是一个实现细节。一个 有趣的推论是,除了少数例外,您可以将 新关键字 的外观视为 代码异味 即可。 - Gary McLean Hall

更新问题的答案:

在您更新的问题中,您将 服务/经理 稍微 进入单个类 - PropertyManager,UserManager。这成为个人喜好。

个人喜欢将它们分开。此外,我还想使用 基于角色和基于声明的授权 。让我用GitHub sample project作为参考。 请随意克隆它。

<强> User Domain

实体框架代码优先Fluent API也使用用户类。

public partial class User
{
   public int Id { get; set; }
   public string UserName { get; set; }
   public string FirstName { get; set; }
}

<强> User Service

public class UserService : IUserService
{
   private readonly IRepository<User> _repository;

   public UserService(IRepository<User> repository)
   {
      _repository = repository;
   }

   public async Task<IPagedList<User>> GetUsersAsync(UserPagedDataRequest request)
   {
   ...
   }
}

<强> Action Method

请注意,与UI相关的业务逻辑保留在UI层。

public async Task<ActionResult> Login(LoginModel model, string returnUrl)
{
   if (ModelState.IsValid)
   {
      bool result = _activeDirectoryService.ValidateCredentials(
         model.Domain, model.UserName, model.Password);
      if (result)
      {
          ...
      }
   }
   ...
}

答案 2 :(得分:1)

你可以采取相当不同的方法.....(忽略你的存储库,但允许它被注入)

在这个系统中,属性只是可读的,有一个事件系统来处理突变,事件系统也有规则系统来控制允许的突变。这意味着即使你有一个属性对象,你也不能在不遵守规则的情况下改变它。

此代码更具概念性。下一个合乎逻辑的步骤是使用完整的actor模型和类似的东西(akka.net),你可能会发现你的存储库模式正在消失:)

public class Property
{
    public string Name { get; private set; }
    private IPropertyRules _rules;
    private List<User> _occupants = new List<User>();
    private IEventLog _eventLog;

    public Property(IPropertyRules rules, IEventLog eventLog)
    {
        _rules = rules;
        _eventLog = eventLog;
    }

    public ActionResult Do(IAction action, User user)
    {
        _eventLog.Add(action, user);
        if (_rules.UserAllowedTo(action, user, this))
        {
            switch (action)
            {
                case Open o:
                    Open();
                    return new ActionResult(true, $"{user} opened {Name}");
                case Enter e:
                    Enter(user);
                    return new ActionResult(true, $"{user} entered {Name}");
            }
            return new ActionResult(false, $"{Name} does not know how to {action} for {user}");
        }
        return new ActionResult(false, $"{user} is not allowed to {action} {Name}");
    }

    private void Enter(User user)
    {
        _occupants.Add(user);
    }

    private void Open()
    {
        IsOpen = true;
    }

    public bool IsOpen { get; set; }
}

public interface IEventLog
{
    void Add(IAction action, User user);
}

public class Enter : IAction
{
}

public interface IPropertyRules
{
    bool UserAllowedTo(IAction action, User user, Property property);
}

public class Open : IAction
{

}

public class ActionResult
{
    public ActionResult(bool successful, string why)
    {
        Successful = successful;
        WhatHappened = why;
    }

    public bool Successful { get; private set; }
    public string WhatHappened { get; private set; }
}

public interface IAction
{
}

public class User
{
}