最近事情很多,有煩惱,有悲傷,不過,一切想通后,感覺其實也沒什么。畢竟,這是每個人都要經歷了,那么恭喜自己,就要當爸爸了,一個程序員爸爸。
所以,好久沒寫博客了,今天,我們繼續來說MEF,這也是MEF的最后一篇博文,這次的主要內容有:
同事的設計
最近同事在做關於WCF的一個項目,而我主要負責WCF通訊的部分,所以無意間看到了他的設計。該同事出生Java,話說Java中的設計模式要比C#起步早很多,但,如果只是為了模式而去設計,那么就失去了模式的意義。
該項目的業務流程大概可以歸結於下圖:
業務邏輯很簡單,有客戶端發送一個命令給服務器端,服務器端根據不同的命令返回不同的結果。這其中的通訊已經通過WCF實現了,所以,現在主要要設計的便是這命令的解析。
同事的設計代碼如下:
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是松耦合的,但是,這是我們所想要的么?其實,恰恰不是,我們期望的是對外提供一個統一的Commad執行接口,並且內部能夠安全方便的添加新的Command支持。如果按照這樣的設計,難免會出現以下問題:
- 由於系統中的Commad很多,導致太多的CommandBase子類,引發子類膨脹性增長。
- CommandBase中的Switch語句會越來越長,與子類的個數相對應。
解決上述問題,我們可以通過改良一些設計,並引入MEF來解決。
最后的設計
其實,解決上述的兩個問題也並不怎么困難,針對第一個問題,我們需要優化下程序的結構,而第二個問題,我們就需要引入MEF了。
首先,我們要理清一下類的職責,CommandBase的設計總讓人感覺怪異,特別是它的靜態方法。所以我們這里改變一下設計,將Command抽象成一個接口,然后由一個實體類來管理Command的調用,基本結構如下:
如此一來,我們解除了繼承導致的依賴,並且,所有的實現都是依賴於抽象的。但是,我們似乎解決了所有問題,又似乎什么問題也沒有解決。有了上面這樣的程序結構,我們解決子類膨脹增長的問題,就很簡單了,主要是接口的設計:
public interface ICommand { bool CanHandle(string command); string Handle(string command); }
通過這樣的接口設計,我們可以把一組相關的Command放在一個類中實現,只要CanHandle返回true,即代表我們可以處理這個Command。例如,我們將先前同事設計的CommandA和CommandB合並為一個類:
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要如何實現,其實也相當的簡單:
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,我們將它實現成了單例模式,現在我們可以這樣來使用:
CommandHandler.Instance.HandleCommandList.Add(new CommandAB()); Console.WriteLine(CommandHandler.Instance.Handle("cmd1")); Console.WriteLine(CommandHandler.Instance.Handle("cmd2"));
能到這一步,已經很不錯了,但是,我們不喜歡手動的Add,所以,現在是MEF最佳的使用時機了(最后完善的代碼):
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很有用處的,由此一來,我們解決了所有問題,使用方式如下:
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了。