我选择使用组合展示某些新行为,而不是将新对象注入使用者代码中,或者让使用者提供自己的新行为实现。我是否做出了错误的设计决定?
我有新的要求,要求我仅在某些情况下才需要执行某些特殊行为。我选择定义一个新接口,在一个专门负责执行行为的具体类中实现新接口。最后,在使用者可以参考的具体类中,我实现了新接口,并将其委托给完成工作的类。
这是我曾经合作过的假设...
我有一个名为IFileManager的接口,该接口允许实现者管理各种类型的文件
我有一个工厂,返回IFileManager的具体实现
我有3个IFileManager的实现,分别是(LocalFileManager,DfsFileManager,CloudFileManager)
我有一个新要求,说我只需要管理由CloudFileManager管理的文件的权限,因此管理权限的行为对于CloudFileManager是唯一的
这是将我引到我编写的代码的测试...
[TestFixture]
public class UserFilesRepositoryTest
{
public interface ITestDouble : IFileManager, IAclManager { }
[Test]
public void CreateResume_AddsPermission()
{
factory.Stub(it => it.GetManager("cloudManager")).Return(testDouble);
repository.CreateResume();
testDouble.AssertWasCalled(it => it.AddPermission());
}
[SetUp]
public void Setup()
{
testDouble = MockRepository.GenerateStub<ITestDouble>();
factory = MockRepository.GenerateStub<IFileManagerFactory>();
repository = new UserFileRepository(factory);
}
private IFileManagerFactory factory;
private UserFileRepository repository;
private ITestDouble testDouble;
}
这是我设计的外壳(这只是基本轮廓,而不是整个shibang)...
public class UserFileRepository
{
// this is the consumer of my code...
public void CreateResume()
{
var fileManager = factory.GetManager("cloudManager");
fileManager.AddFile();
// some would argue that I should inject a concrete implementation
// of IAclManager into the repository, I am not sure that I agree...
var permissionManager = fileManager as IAclManager;
if (permissionManager != null)
permissionManager.AddPermission();
else
throw new InvalidOperationException();
}
public UserFileRepository(IFileManagerFactory factory)
{
this.factory = factory;
}
private IFileManagerFactory factory;
}
public interface IFileManagerFactory
{
IFileManager GetManager(string managerName);
}
public class FileManagerFactory : IFileManagerFactory
{
public IFileManager GetManager(string managerName)
{
IFileManager fileManager = null;
switch (managerName) {
case "cloudManager":
fileManager = new CloudFileManager();
break;
// other managers would be created here...
}
return fileManager;
}
}
public interface IFileManager
{
void AddFile();
void DeleteFile();
}
public interface IAclManager
{
void AddPermission();
void RemovePermission();
}
/// <summary>
/// this class has "special" behavior
/// </summary>
public class CloudFileManager : IFileManager, IAclManager
{
public void AddFile() {
// implementation elided...
}
public void DeleteFile(){
// implementation elided...
}
public void AddPermission(){
// delegates to the real implementation
aclManager.AddPermission();
}
public void RemovePermission() {
// delegates to the real implementation
aclManager.RemovePermission();
}
public CloudFileManager(){
aclManager = new CloudAclManager();
}
private IAclManager aclManager;
}
public class LocalFileManager : IFileManager
{
public void AddFile() { }
public void DeleteFile() { }
}
public class DfsFileManager : IFileManager
{
public void AddFile() { }
public void DeleteFile() { }
}
/// <summary>
/// this class exists to manage permissions
/// for files in the cloud...
/// </summary>
public class CloudAclManager : IAclManager
{
public void AddPermission() {
// real implementation elided...
}
public void RemovePermission() {
// real implementation elided...
}
}
最佳答案
您添加新行为的方法只为您节省了事情的初始化工作,因为无论如何您都将CloudAclManager
与CloudFileManager
分开实现。我不同意它如何与您现有的设计集成(这还不错)...
这有什么问题?
您分离了文件管理器并使用了IFileManager
,但是对IAclManager
却没有这样做。在拥有创建各种文件管理器的工厂的同时,您自动将CloudAclManager
设置为IAclManager
的CloudFileManager
。那么,拥有IAclManager
有什么意义呢?
更糟的是,你
初始化一个新的CloudAclManager
每次尝试获取其ACL时都在CloudFileManager
内部
经理-你刚给工厂
对您的责任CloudFileManager
。
在将CloudFileManager
作为属性的基础上,您可以实现IAclManager
。您刚刚将规则设置为CloudFileManager
独有的权限移至模型层而不是业务规则层。这也导致支持不必要的
自我和财产之间循环引用的潜力。
即使你想要CloudFileManager
委托
权限功能CloudAclManager
,为什么会误导他人
认为CloudFileManager
自行处理
权限集?你刚刚做了
模型类看起来像立面。
好,那我该怎么办呢?
首先,您将类命名为CloudFileManager
,这是正确的,因为它的唯一职责是为云管理文件。现在,还必须为云管理权限集,CloudFileManager
承担这些新职责真的正确吗?答案是不。
这并不是说您不能在同一类中具有用于管理文件的代码和用于管理权限的代码。但是,将类命名为更通用的名称(如CloudFileSystemManager
)会更有意义,因为其职责将不仅限于文件或权限。
不幸的是,如果您重命名您的班级,将会对当前正在使用您班级的人产生负面影响。那么如何仍然使用合成而不更改CloudFileManager
呢?
我的建议是执行以下操作:
1.保留您的IAclManager
并创建IFileSystemManager
public interface IFileSystemManager {
public IAclManager AclManager { get; }
public IFileManager FileManager { get; }
}
要么
public interface IFileSystemManager : IAclManager, IFileManager {
}
2.创建
CloudFileSystemManager
public class CloudFileSystemManager : IFileSystemManager {
// implement IFileSystemManager
//
// How each manager is set is up to you (i.e IoC, DI, simple setters,
// constructor parameter, etc.).
//
// Either way you can just delegate to the actual IAclManager/IFileManager
// implementations.
}
为什么?
这将使您可以使用新行为,而对当前代码库/功能的影响最小,而不会影响正在使用原始代码的用户。文件管理和权限管理也可以同时进行(即在尝试实际的文件操作之前检查权限)。如果您需要任何其他权限集管理器或任何其他类型的管理器,它也可以扩展。
编辑-包括问问者的澄清问题
如果创建
IFileSystemManager : IFileManager, IAclManager
,存储库是否仍将使用FileManagerFactory并返回CloudFileSystemManager的实例?不,
FileManagerFactory
不应返回FileSystemManager
。您的外壳必须进行更新才能使用新的接口/类。也许像下面这样:private IAclManagerFactory m_aclMgrFactory;
private IFileManagerFactory m_fileMgrFactory;
public UserFileRepository(IAclManagerFactory aclMgrFactory, IFileManagerFactory fileMgrFactory) {
this.m_aclMgrFactory = aclMgrFactory;
this.m_fileMgrFactory = fileMgrFactory;
}
public void CreateResume() {
// I understand that the determination of "cloudManager"
// is non-trivial, but that part doesn't change. For
// your example, say environment = "cloudManager"
var environment = GetEnvMgr( ... );
var fileManager = m_fileMgrFactory.GetManager(environment);
fileManager.AddFile();
// do permission stuff - see below
}
至于调用权限的事情,您有两种选择:
// can use another way of determining that a "cloud" environment
// requires permission stuff to be done
if(environment == "cloudManager") {
var permissionManager = m_aclMgrFactory.GetManager(environment);
permissionManager.AddPermission();
}
要么
// assumes that if no factory exists for the environment that
// no permission stuff needs to be done
var permissionManager = m_aclMgrFactory.GetManager(environment);
if (permissionManager != null) {
permissionManager.AddPermission();
}
关于c# - 是否应该通过组合或其他方式引入新的行为?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/4332331/