Recently I did design review for a simple scenario that roughly corresponds to Command pattern extensible with inspection or modification of information prior to and subsequent to command execution. Simple design for a simple scenario. Though it wasn’t the case.
interface ICommand { object Do(object arg); } interface ICommandInspector { object BeforeCall(object arg); object AfterCall(object ret); } abstract class Command<T> : ICommand where T : ICommandInspector, new() { public object Do(object arg) { var insp = new T(); arg = insp.BeforeCall(arg); var ret = DoCore(arg); return insp.AfterCall(ret); } protected abstract object DoCore(object arg); }
Besides Command pattern itself one more concept was introduced – command inspector that handles pre and post processing requirement. By the way does this concept sound familiar to you?
For example, the following inspector measures command execution time.
class PerformanceCounterInspector : ICommandInspector { private readonly Stopwatch m_sw = new Stopwatch(); public object BeforeCall(object arg) { m_sw.Start(); return arg; } public object AfterCall(object ret) { m_sw.Stop(); Console.WriteLine(m_sw.Elapsed); return ret; } }
The solution addresses the requirements. So, where is the problem? Complexity! Let us not forget that managing complexity is primary topic in software development. Simpler solution that does the job should be preferable.
Proposed solution makes developers to divide tasks into command and inspector aspects although the only consumer of inspector concept is command itself and seems it can be a part of it. On the other hand it is not that easy to understand what inspector does unless you have good understanding of how it is consumed by the command. A complex solution to a simple problem.
We can try to keep solution as simple as possible from employed concepts perspective. How to employ only Command concept but still address requirements? This is where Decorator pattern is handy.
abstract class CommandDecorator : ICommand { private readonly ICommand m_cmd; protected CommandDecorator(ICommand cmd) { m_cmd = cmd; } public virtual object Do(object arg) { return m_cmd.Do(arg); } }
It is easier to understand the intent of added functionality as it is not separated from command concept.
class PerformanceCounterCommandDecorator : CommandDecorator { public PerformanceCounterCommandDecorator(ICommand cmd) : base(cmd) { } public override object Do(object arg) { var sw = new Stopwatch(); sw.Start(); var ret = base.Do(arg); sw.Stop(); Console.WriteLine(sw.Elapsed); return ret; } }
The Decorator pattern naturally promotes dynamic functionality composition which is especially important when there are several ways to compose functionality. On the other hand decorator pattern promotes composition over inheritance (good thing as inheritance violates encapsulation and may lead to fragile designs).
Summary:
- DO NOT introduce new concepts into your design unless you have strong reasons to do so
- DO always assess complexity of your design
- DO strive for concepts that support composition
No comments:
Post a Comment