我目前的班级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和逻辑安全性,并且尽可能接近实际实现。
你们都在想什么?
答案 0 :(得分:2)
我认为我同意您的 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;
}
}
注意:只需提取IPropertyManager
& IUserManager
以便调用类将依赖于接口并提供实现。
如果您希望(您应该)强制客户端提供IPropertyRepo
和IUserRepo
接口的具体实现,那么创建无参数构造函数是没用的。
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
{
}