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



我当前的类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的新实例,然后它可以完成其工作。

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

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,他每天早上在工作中所做的工作之一就是Open()Property(即,他解锁了他ManagerProperty)。我还有一个UserManger,负责管理访问Property的人,她的名字是"莎拉",她有一个叫做EnterProperty()的功能(这是她早上走进大楼时所做的)。

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

根据所有公认的标准,这看起来像这样:

物业经理

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...");
}
}
}

网页接口控制器

{
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 而不是紧密耦合的类以及其他好处。

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

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

网页接口控制器

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???
}
}

UserManager上的EnterProperty()方法现在如下所示: 这允许莎拉实际进入建筑物 公共无效 EnterProperty() { Console.WriteLine("我在大楼里"); }

上面承诺的解释:

如果我们从现实世界的角度思考,我们必须同意后者优于前者。 当想到存储库时,假设这是对自我(即一个人的人)的定义(即,拥有与User相关的所有数据的UserRepoUserManager就像DNA,心跳,脑电波模式等对人类(HumanRepo)。 因此,允许UserManager了解PropertyRepo访问其Open()方法违反了所有现实世界的安全原则和业务规则。 实际上,这说明通过 My Contructor(),我可以获得一个PropertyRepo的接口表示,我可以以任何我认为合适的方式使用它。 这是以下HumanRepo逻辑的同义词:

我,莎拉 - 一个UserManager- 通过一个新的实例,通过我的Constructor()PropertyRepo的满意,创建了一个罗伯特的全息图界面,PropertyManager我可以使用任何我认为合适的方式。 当然,现在我只想使用PropertyRepoIsOpen()方法,如果罗伯特还没有履行职责,我实际上使用Open()方法自己做。这对我来说是一个安全问题。在现实世界中,这说明我不必等待 Robert 打开Property并使用他的全息副本并实现他的Open()方法来获取访问权限。

这似乎不对。

我认为通过最后一个实现,我得到了SoC,SRP,DI,存储库模式,TDD和逻辑安全性,并尽可能接近现实世界的实现。

大家怎么看?

我想我同意你的SoC并将PropertyManager类分为PropertyManager类和UserManager类。你快到了。

我只会重构,如下所示:

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;
}
}

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

如果要(应该)强制客户端提供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 的依赖项。

从您在此处发布的内容来看,差不多就是这样。

工作单元模式用于不在管理器层中的存储库层。我会从标题中删除它。

希望这有帮助!

我认为这可以实现一些 OOP 目标,例如分离关注点 以及单一责任原则。

结果恰恰相反。现在,物业经理物业回购紧密结合;以前,它们是松散耦合的。

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

接口 描述可以做什么,而类描述如何完成。只有类涉及实现细节——接口完全不知道某事是如何完成的。因为只有类才有构造函数,所以构造函数是一个实现细节。一 有趣的是,除了少数例外情况外,您可以将new 关键字的外观视为代码异味。- 加里·麦克莱恩·霍尔

更新问题的答案:

在您更新的问题中,您将服务/管理器一些组合成一个类 - 属性管理器,用户管理器。这成为个人喜好。

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

用户域

用户类也由实体框架代码第一流利 API 使用。

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

用户服务

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 层。

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

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

在此系统中,属性仅可读,具有事件系统来处理突变,事件系统还具有控制允许哪些突变的规则系统。 这意味着即使您有一个属性对象,如果不通过其规则,您也无法改变它。

此代码更具概念性。 下一个合乎逻辑的步骤是使用完整的参与者模型和类似 (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
{
}

最新更新