最近事情很多,有烦恼,有悲伤,不过,一切想通后,感觉其实也没什么。毕竟,这是每个人都要经历了,那么恭喜自己,就要当爸爸了,一个程序员爸爸。
同事的设计
最近同事在做关于WCF的一个项目,而我主要负责WCF通讯的部分,所以无意间看到了他的设计。该同事出生Java,话说Java中的设计模式要比C#起步早很多,但,如果只是为了模式而去设计,那么就失去了模式的意义。
该项目的业务流程大概可以归结于下图:
业务逻辑很简单,有客户端发送一个命令给服务器端,服务器端根据不同的命令返回不同的结果。这其中的通讯已经通过WCF实现了,所以,现在主要要设计的便是这命令的解析。
同事的设计代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| using System;
public abstract class CommandBase {
public static string Execute(string command) {
CommandBase cmd = null; switch (command) { case "cmd1": cmd = new CommandA(); break; case "cmd2": cmd = new CommandB(); break; default: break; }
if (cmd != null) return cmd.Execute(); return string.Empty; }
protected virtual string Execute() {
return string.Empty; }
}
public class CommandA : CommandBase { protected override string Execute() { return "CommandA"; } }
public class CommandB : CommandBase { protected override string Execute() { return "CommandB"; } }
|
看上去蛮好,有多态,有继承,并且类的耦合度不高。是蛮好的,但是我觉得不适合,有点为了模式而设计的嫌疑。具体有什么问题?那么我们继续就这个设计,来做进一步的讨论。
针对同事设计随想
首先,我们看看它的类关系图:
由于CommandA
和CommandB
继承至CommandBase
,所以,它们与CommandBase
是强耦合关系,并且CommandBase
中的静态方法引用了CommandA
、CommandB
,所以,CommandBase
对CommandA
、CommandB
又有依赖关系。
如此一来,我们发现整个系统中,只有CommandA
和CommandB
是松耦合的,但是,这是我们所想要的么?其实,恰恰不是,我们期望的是对外提供一个统一的Command
执行接口,并且内部能够安全方便的添加新的Command
支持。如果按照这样的设计,难免会出现以下问题:
- 由于系统中的
Command
很多,导致太多的CommandBase
子类,引发子类膨胀性增长。
CommandBase
中的Switch
语句会越来越长,与子类的个数相对应。
解决上述问题,我们可以通过改良一些设计,并引入MEF来解决。
最后的设计
其实,解决上述的两个问题也并不怎么困难,针对第一个问题,我们需要优化下程序的结构,而第二个问题,我们就需要引入MEF了。
首先,我们要理清一下类的职责,CommandBase
的设计总让人感觉怪异,特别是它的静态方法。所以我们这里改变一下设计,将Command
抽象成一个接口,然后由一个实体类来管理Command
的调用,基本结构如下:
如此一来,我们解除了继承导致的依赖,并且,所有的实现都是依赖于抽象的。但是,我们似乎解决了所有问题,又似乎什么问题也没有解决。有了上面这样的程序结构,我们解决子类膨胀增长的问题,就很简单了,主要是接口的设计:
1 2 3 4
| public interface ICommand { bool CanHandle(string command); string Handle(string command); }
|
通过这样的接口设计,我们可以把一组相关的Command
放在一个类中实现,只要CanHandle
返回true
,即代表我们可以处理这个Command
。例如,我们将先前同事设计的CommandA
和CommandB
合并为一个类:
1 2 3 4 5 6 7 8 9 10 11 12
| public class CommandAB : ICommand { public bool CanHandle(string command) { return command == "cmd1" || command == "cmd2"; }
public string Handle(string command) { if (command == "cmd1") return "CommandA"; else return "CommandB"; } }
|
下面最重要的,是我们的CommandHandler
要如何实现,其实也相当的简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public sealed class CommandHandler { private static readonly CommandHandler _instance = new CommandHandler(); public static CommandHandler Instance { get { return _instance; } }
public IList<ICommand> HandleCommandList { get; private set; }
private CommandHandler() { this.HandleCommandList = new List<ICommand>(); }
public string Handle(string command) { foreach (var cmd in this.HandleCommandList) { if (cmd.CanHandle(command)) return cmd.Handle(command); } return string.Empty; } }
|
仔细看看上面的代码,其实很简单。为了统一管理ICommand
,我们将它实现成了单例模式,现在我们可以这样来使用:
1 2 3 4
| CommandHandler.Instance.HandleCommandList.Add(new CommandAB());
Console.WriteLine(CommandHandler.Instance.Handle("cmd1")); Console.WriteLine(CommandHandler.Instance.Handle("cmd2"));
|
能到这一步,已经很不错了,但是,我们不喜欢手动的Add
,所以,现在是MEF最佳的使用时机了(最后完善的代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public interface ICommand { bool CanHandle(string command); string Handle(string command); }
[Export, PartCreationPolicy(CreationPolicy.Shared)] public sealed class CommandHandler {
[ImportMany] private IEnumerable<ICommand> _handleCommandList;
private CommandHandler() { }
public string Handle(string command) { foreach (var cmd in _handleCommandList) { if (cmd.CanHandle(command)) return cmd.Handle(command); } return string.Empty; } }
[InheritedExport(typeof(ICommand))] public abstract class CommandBase : ICommand { public abstract bool CanHandle(string command); public abstract string Handle(string command); }
public class CommandAB : CommandBase { public override bool CanHandle(string command) { return command == "cmd1" || command == "cmd2"; }
public override string Handle(string command) { if (command == "cmd1") return "CommandA"; else return "CommandB"; } }
|
细看代码,你还是会觉得MEF很有用处的,由此一来,我们解决了所有问题,使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private static CompositionContainer _container; static void Main(string[] args) {
var catalog = new AssemblyCatalog(typeof(Program).Assembly); _container = new CompositionContainer(catalog);
var commandHandler = _container.GetExportedValue<CommandHandler>(); Console.WriteLine(commandHandler.Handle("cmd1")); Console.WriteLine(commandHandler.Handle("cmd2"));
while (true) { Console.ReadLine(); } }
|
至此,我们所依赖的,是MEF的容器,后续增加新的Command
也会很方便,对外的统一接口更不会改变。通过这一个例子,我们综合性的感受了下MEF的使用,而到这里,MEF的系列已经接近尾声了。
那么,恭喜你,你应该已经学会使用MEF了。