MEF核心筆記(5)是模式還是設計


最近事情很多,有煩惱,有悲傷,不過,一切想通后,感覺其實也沒什么。畢竟,這是每個人都要經歷了,那么恭喜自己,就要當爸爸了,一個程序員爸爸。

所以,好久沒寫博客了,今天,我們繼續來說MEF,這也是MEF的最后一篇博文,這次的主要內容有:

同事的設計

最近同事在做關於WCF的一個項目,而我主要負責WCF通訊的部分,所以無意間看到了他的設計。該同事出生Java,話說Java中的設計模式要比C#起步早很多,但,如果只是為了模式而去設計,那么就失去了模式的意義。

該項目的業務流程大概可以歸結於下圖:

image

業務邏輯很簡單,有客戶端發送一個命令給服務器端,服務器端根據不同的命令返回不同的結果。這其中的通訊已經通過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";
    }
}

看上去蠻好,有多態,有繼承,並且類的耦合度不高。是蠻好的,但是我覺得不適合,有點為了模式而設計的嫌疑。具體有什么問題?那么我們繼續就這個設計,來做進一步的討論。

針對同事設計隨想

首先,我們看看它的類關系圖:

image

由於CommandA和CommandB繼承至CommandBase,所以,它們與CommandBase是強耦合關系,並且CommandBase中的靜態方法引用了CommandA、CommandB,所以,CommandBase對CommandA、CommandB又有依賴關系。

如此一來,我們發現整個系統中,只有CommandA和CommandB是松耦合的,但是,這是我們所想要的么?其實,恰恰不是,我們期望的是對外提供一個統一的Commad執行接口,並且內部能夠安全方便的添加新的Command支持。如果按照這樣的設計,難免會出現以下問題:

  • 由於系統中的Commad很多,導致太多的CommandBase子類,引發子類膨脹性增長。
  • CommandBase中的Switch語句會越來越長,與子類的個數相對應。

解決上述問題,我們可以通過改良一些設計,並引入MEF來解決。

最后的設計

其實,解決上述的兩個問題也並不怎么困難,針對第一個問題,我們需要優化下程序的結構,而第二個問題,我們就需要引入MEF了。

首先,我們要理清一下類的職責,CommandBase的設計總讓人感覺怪異,特別是它的靜態方法。所以我們這里改變一下設計,將Command抽象成一個接口,然后由一個實體類來管理Command的調用,基本結構如下:

image

如此一來,我們解除了繼承導致的依賴,並且,所有的實現都是依賴於抽象的。但是,我們似乎解決了所有問題,又似乎什么問題也沒有解決。有了上面這樣的程序結構,我們解決子類膨脹增長的問題,就很簡單了,主要是接口的設計:

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了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM