是否应该通过组合或其他方式引入新行为?
我选择使用组合来公开一些新行为,而不是将新对象注入到我的消费者代码中,或者让消费者提供自己的新行为实现。我是否做出了错误的设计决定?
我有新的要求,要求我只需要在某些情况下实施一些特殊行为。我选择定义一个新接口,在一个单独负责执行行为的具体类中实现新接口。最后,在消费者引用的具体类中,我实现了新接口并将其委托给执行该工作的类。
以下是我正在使用的假设...
- 我有一个名为 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...
}
}
I chose to expose some new behavior using composition vs. injecting a new object into my consumers code OR making the consumer provide their own implementation of this new behavior. Did I make a bad design decision?
I had new requirements that said that I needed to implement some special behavior in only certain circumstances. I chose to define a new interface, implement the new interface in a concrete class that was solely responsible for carrying out the behavior. Finally, in the concrete class that the consumer has a reference to, I implemented the new interface and delegate down to the class that does the work.
Here are the assumptions that I was working with...
- I haven an interface, named IFileManager that allows implementors to manage various types of files
- I have a factory that returns a concrete implementation of IFileManager
- I have 3 implementations of IFileManager, these are (LocalFileManager, DfsFileManager, CloudFileManager)
- I have a new requirements that says that I need to manage permissions for only the files being managed by the CloudFileManager, so the behavior for managing permissions is unique to the CloudFileManager
Here is the test that led me to the code that I wrote...
[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;
}
Here is the shell of my design (this is just the basic outline not the whole 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...
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您添加新行为的方法只是在总体方案中节省了初始化工作,因为无论如何您都将
CloudAclManager
与CloudFileManager
分开实现。我不同意一些关于如何将其与您现有设计集成的事情(这还不错)......这有什么问题吗?
IFileManager
,但没有对IAclManager
进行同样的操作。当您有一个工厂来创建各种文件管理器时,您会自动将CloudAclManager
设为CloudFileManager
的IAclManager
。那么,拥有IAclManager
有什么意义呢?初始化一个新的
CloudAclManager
每次尝试获取其 ACL 时,都在
CloudFileManager
内部经理-你刚刚给了工厂
对你的责任
云文件管理器
。CloudFileManager
实现了IAclManager
。您刚刚将权限对于CloudFileManager
唯一的规则移至模型层而不是业务规则层。这也导致支持不必要的self 和 property 之间循环引用的潜力。
CloudFileManager
委托权限功能
CloudAclManager
,为什么误导别人类认为
CloudFileManager
处理自己的权限集?你刚刚做了你的
模型类看起来像一个门面。
好吧,那么我应该做什么呢?
首先,您将类命名为
CloudFileManager
,这是正确的,因为它的唯一职责是管理云文件。既然云的权限集也必须进行管理,那么CloudFileManager
承担这些新职责真的正确吗?答案是否定的。这并不是说您不能在同一个类中使用管理文件的代码和管理权限的代码。但是,将该类命名为更通用的名称(例如 CloudFileSystemManager)会更有意义,因为它的职责不仅限于文件或权限。
不幸的是,如果您重命名您的类,则会对当前使用您的类的人产生负面影响。那么,仍然使用组合,但不更改 CloudFileManager 怎么样?
我的建议是执行以下操作:
1。保留您的
IAclManager
并创建IFileSystemManager
或
2。创建
CloudFileSystemManager
为什么?
这将允许您在使用新行为时对当前代码库/功能的影响最小,而不会影响那些使用原始代码的人。文件管理和权限管理也可以同时进行(即在尝试实际文件操作之前检查权限)。如果您需要任何其他权限集管理器或任何其他类型的管理器,它也是可扩展的。
编辑 - 包括提问者的澄清问题
如果我创建
IFileSystemManager : IFileManager, IAclManager
,存储库是否仍使用 FileManagerFactory 并返回 CloudFileSystemManager 的实例?不,
FileManagerFactory
不应返回FileSystemManager
。您的 shell 必须更新才能使用新的接口/类。也许类似于以下内容:至于调用要完成的权限,您有几个选择:
或
Your approach to add your new behavior only saved you an initialization in the grand scheme of things because you to implemented
CloudAclManager
as separate fromCloudFileManager
anyways. I disagree with some things with how this integrates with your existing design (which isn't bad)...What's Wrong With This?
IFileManager
, but you didn't do the same withIAclManager
. While you have a factory to create various file managers, you automatically madeCloudAclManager
theIAclManager
ofCloudFileManager
. So then, what's the point of havingIAclManager
?initialize a new
CloudAclManager
inside of
CloudFileManager
every time you try to get its ACLmanager - you just gave factory
responsibilities to your
CloudFileManager
.CloudFileManager
implementIAclManager
on top of having it as a property. You just moved the rule that permissions are unique toCloudFileManager
into your model layer rather than your business rule layer. This also results in supporting the unnecessarypotential of circular referencing between self and property.
CloudFileManager
to delegate thepermission functionality to
CloudAclManager
, why mislead otherclasses into thinking that
CloudFileManager
handles its ownpermission sets? You just made your
model class look like a facade.
Ok, So What Should I Do Instead?
First, you named your class
CloudFileManager
, and rightly so because its only responsibility is to manage files for a cloud. Now that permission sets must also be managed for a cloud, is it really right for aCloudFileManager
to take on these new responsibilities? The answer is no.This is not to say that you can't have code to manage files and code to manage permissions in the same class. However, it would then make more sense for the class to be named something more general like
CloudFileSystemManager
as its responsibilities would not be limited to just files or permissions.Unfortunately, if you rename your class it would have a negative effect on those currently using your class. So how about still using composition, but not changing
CloudFileManager
?My suggestion would be to do the following:
1. Keep your
IAclManager
and createIFileSystemManager
or
2. Create
CloudFileSystemManager
Why?
This will allow you to use your new behavior with minimal impact to your current code base / functionality without affecting those who are using your original code. File management and permission management can also coincide (i.e. check permissions before attempting an actual file action). It's also extensible if you need any other permission set manager or any other type of managers for that matter.
EDIT - Including asker's clarification questions
If I create
IFileSystemManager : IFileManager, IAclManager
, would the repository still use the FileManagerFactory and return an instance of CloudFileSystemManager?No, a
FileManagerFactory
should not return aFileSystemManager
. Your shell would have to update to use the new interfaces/classes. Perhaps something like the following:As for invoking permission stuff to be done, you have a couple options:
or
我认为构图正是实现这种技巧的正确手段。但我认为您应该保持更简单(KISS),只需在 IFileManager 中创建一个 IAclManager 属性并将其默认设置为 null 并在那里设置云服务的 SecurityManager 实现。
这有不同的优点:
当您执行任务(添加或删除文件)时,您可以再次检查是否有权限管理器以及是否授予了权限,如果没有则抛出异常(因为当缺少执行某项操作的权限,而不是一般情况下缺少权限(如果您不打算添加或删除文件)。
当您的客户下次更改需求时,您可以像现在一样为其他 IFileManager 实现更多 IAclManager。
哦,那么当其他人查看代码时,您就不会有如此令人困惑的层次结构;-)
I think that composition is exactly the right means to to this kind of trick. But I think you should keep it more simple (KISS) and just make an IAclManager property in the IFileManager and set it to null by default and set the SecurityManager implementation for the cloud service there.
This has different upsides:
When you then carry out the task (to add or delete a file), you can check again if there's a permissionsManager and if the permission is given, if not throw exception (as you'll want to throw the exception when a permission to do an action is missing, not if a permission is missing in general if you're not going to add or delete files).
You can later on implement more IAclManagers for the other IFileManagers when your customer changes the requirements next time the same way as you would now.
Oh, and then you won't have such a confusing hierarchy when somebody else looks at the code ;-)
总的来说,它看起来不错,但我确实有一些建议。看来您的 CreateResume() 方法实现需要一个 IFileManager,它也是一个 IAclManager (否则它会引发异常)。
如果是这种情况,您可能需要考虑向 GetManager() 方法添加一个重载,在该方法中您可以指定所需的接口,并且工厂可以拥有在找不到正确的接口时抛出异常的代码。文件管理器。为了实现这一点,您可以添加另一个空接口,但同时实现 IAclManager 和 IFileManager:
然后将以下方法添加到工厂:
如果给定名称的管理器未实现 T,GetManager 将抛出异常(您也可以检查如果它派生自 T 类型或者也是 T 类型)。
话虽这么说,如果 AddPermissions 不接受任何参数(不确定您是否只是为了这篇文章而这样做),为什么不直接从 CloudFileManager.AddFile() 方法调用 AddPermissions() 并将其完全封装在用户之外(删除是否需要新的 IAclManager 接口)?
无论如何,在 CreateResume() 方法中调用 AddFile 然后抛出异常似乎不是一个好主意(因为您现在创建了一个没有正确权限的文件,这可能是一个安全问题,而且消费者收到异常,因此他可能认为 AddFile 没有成功,而不是 AddPermission)。
祝你好运!
In general it looks good, but I do have a few suggestions. It seems that your CreateResume() method implementation demands a IFileManager that is also an IAclManager (or else it throws an exception).
If that is the case, you may want to consider adding an overload to your GetManager() method in which you can specify the interface that you require, and the factory can have the code that throws an exception if it doesn't find the right file manager. To accompolish this you can add another interface that is empty but implements both IAclManager and IFileManager:
And then add the following method to the factory:
GetManager will throw an exception if the manager with the name given doesn't implement T (you can also check if it derives from or is of type T also).
All that being said, if AddPermissions doesn't take any parameters (not sure if you just did this for the post), why not just call AddPermissions() from CloudFileManager.AddFile() method and have it completely encapsulated from the user (removing the need for the new IAclManager interface)?
In any event, doesn't seem like a good idea to call AddFile in the CreateResume() method and only then throw the exception (since you now you have now created a file without the correct permissions which could be a security issue and also the consumer got an exception so he may assume that AddFile didn't succeed, as opposed to AddPermission).
Good luck!