首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单一责任原则关注(我是否考虑过适当的重构)

单一责任原则关注(我是否考虑过适当的重构)
EN

Stack Overflow用户
提问于 2017-03-15 21:59:49
回答 3查看 170关注 0票数 0

我当前的类PropertyManager如下所示:

代码语言:javascript
复制
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的一个新实例,然后它可以完成它的工作。

我认为这实现了一些设计原则,如关注点分离和单一责任分离,但是我可能会摆脱依赖注入设计模式,因为新的管理人员现在将拥有多个构造器,如下所示:

代码语言:javascript
复制
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");
    }
}

会不会对此不屑一顾?还是我在正确的轨道上?在这两种情况下,为什么和感谢?

Update.在阅读了Yawar的帖子后,决定更新我的帖子,我想我有一个相关的问题。

让我们来看看上面的一个真实世界的例子。在现实生活中,我有一个名为"Robert“的PropertyManager,他每天早上在工作中所做的工作之一就是打开Open() Property (也就是说,他打开了他是ManagerProperty )。我还有一个UserManger,负责管理访问Property的人,她的名字叫"Sarah“,她有一个叫做EnterProperty()的函数(这就是她早上走进大楼时所做的事情)。

规则: UserManager在使用EnterProperty()时依赖于PropertyManager

按照所有公认的标准,这看起来是这样的:

属性管理器

代码语言:javascript
复制
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();
    }
}

用户管理器

代码语言:javascript
复制
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...");
        }
    }
}

网络API控制器

代码语言:javascript
复制
{
    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();
    }
}

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

然而,真正的现实吗?(解释我为什么第二次问)

我认为一种更真实(现实)的方法就是:

网络API控制器

代码语言:javascript
复制
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物理地进入建筑物的公共空EnterProperty() {Console.WriteLine(“I‘s in the building”);}。

从上面承诺的解释:

如果我们认为在现实世界中,,我们必须同意,后者优先于前者。当想到一个存储库时,让我们说这是一个人的自我(即人)的定义(即,拥有与UserRepo相关的所有数据的User,即与UserManager有关的DNA、心跳、脑波模式等)是人类( HumanRepo)的定义。因此,允许UserManager了解PropertyRepo Open()方法违反了所有真实世界安全原则和业务规则。实际上,这意味着通过我的Contructor(),我可以获得一个PropertyRepo的接口表示,我可以使用我认为合适的任何方式。这是HumanRepo的以下逻辑的同义词

我,萨拉-一个UserManager -通过一个新的实例,通过我的PropertyRepo通过构造器()创建一个PropertyRepo的全息界面,它是我认为合适的任何方式都可以使用的PropertyManager。当然,现在我只想使用PropertyRepoPropertyRepo方法,如果罗伯特还没有完成他的职责,我实际上使用Open()方法来完成它。,这是我关心的一个安全问题,在现实世界中说,我不需要等待罗伯特打开Property,使用他的全息文件,实现他的Open()方法来访问。

这似乎不对。

我认为,在最后一个实现中,我得到了SoC、SRP、DI、Repository模式、TDD和Logical,并且尽可能接近于现实世界的实现。

你们都怎么想?

EN

回答 3

Stack Overflow用户

发布于 2017-03-15 22:25:02

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

我只想重构如下:

代码语言:javascript
复制
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接口的具体实现,则创建无参数构造函数是无用的。

代码语言:javascript
复制
public PropertyManager(){
        // use the default repo
        _propertyRepo = new PropertyRepo();
    }

我觉得你不需要

代码语言:javascript
复制
if (propertyRepo == null)
    throw new ArgumentNullException("propertyRepo");

代码语言:javascript
复制
if (userRepo == null)
    throw new ArgumentNullException("userRepo");

由于IPropertyRepo和IUserRepo将在应用程序启动时通过IoC进行解析(比如它的MVC,然后在调用控制器之前IoC会解决它们),所以不需要检查null。我从未在代码中检查过null的依赖项。

从你在这里发布的信息来看,差不多就是这样了。

工作单位模式用于存储库层,而不是管理层。我会把它从标题中删除。

希望这能有所帮助!

票数 2
EN

Stack Overflow用户

发布于 2017-03-15 22:38:57

我认为这实现了一些面向对象的目标,例如分离关注点和单一责任原则。

结果正好相反。现在,PropertyManagerPropertyRepo紧密结合;以前,它们是松散耦合的。

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

接口描述可以做什么,而类描述它是如何完成的。只有类涉及实现细节--接口完全不知道如何完成某些任务。因为只有类有构造函数,因此构造函数是实现细节。这的一个有趣的推论是,除了几个例外,您可以考虑新关键字的外观是一个代码嗅觉。- 加里McLean厅

对最新问题的回答:

在您更新的问题中,您将Service/Manager组合成一个单独的类- PropertyManager,UserManager。这就变成了个人偏好。

我个人喜欢把它们分开。此外,我喜欢使用基于角色和基于声明的授权。让我用我的GitHub样本项目作为参考。请随意克隆它。

用户域

Fluent API也使用User类。

代码语言:javascript
复制
public partial class User
{
   public int Id { get; set; }
   public string UserName { get; set; }
   public string FirstName { get; set; }
}

用户服务

代码语言:javascript
复制
public class UserService : IUserService
{
   private readonly IRepository<User> _repository;

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

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

动作法

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

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2017-03-16 04:32:39

您可以采取完全不同的方法.(忽略存储库,但允许注入存储库)

在该系统中,该属性只具有可读性,有一个事件系统来处理突变,事件系统还具有规则系统,该规则系统控制允许哪些突变。这意味着,即使您有一个属性对象,也不能在不遍历其规则的情况下对其进行变异。

这段代码更具有概念性。下一个逻辑步骤是使用一个完整的参与者模型,类似于(akka.net),您可能会发现您的存储库模式正在消失:)

代码语言:javascript
复制
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
{
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42821477

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档