1. 程式人生 > >C#進階系列——MEF實現設計上的“鬆耦合”(一)

C#進階系列——MEF實現設計上的“鬆耦合”(一)

前言:最近去了趟外地出差,介紹推廣小組開發的框架類產品。推廣物件是本部門在專案上面的同事——1到2年工作經驗的初級程式設計師。在給他們介紹框架時發現很多框架設計層面的知識他們都沒有接觸過,甚至沒聽說過,這下囧了~~於是乎在想該如何跟他們解釋MEF、AOP、倉儲模式等方面的東東。本來 C#基礎系列 應該還有兩篇關於非同步的沒有寫完,奈何現在要推廣這些個東西,博主打算先介紹下專案中目前用到的些技術,非同步的往後有時間再做分享。C#進階系列主要圍繞MEF、AOP、倉儲模式、Automapper、WCF等展開。本篇先來介紹下MEF的基礎知識。

1、什麼是MEF

先來看msdn上面的解釋:MEF(Managed Extensibility Framework)是一個用於建立可擴充套件的輕型應用程式的庫。 應用程式開發人員可利用該庫發現並使用擴充套件,而無需進行配置。 擴充套件開發人員還可以利用該庫輕鬆地封裝程式碼,避免生成脆弱的硬依賴項。 通過 MEF,不僅可以在應用程式內重用擴充套件,還可以在應用程式之間重用擴充套件。

也有人把MEF解釋為“依賴注入”的一種方式,那麼什麼是“依賴注入”?如果這樣解釋,感覺越陷越深......根據博主的理解,瞭解MEF只需要抓住以下幾個關鍵點:

(1)字面意思,可擴充套件的framework,或者叫可擴充套件的庫。也就是說,使用MEF是為了提高程式的可擴充套件性。MEF會根據指定的匯入匯出自動去發現匹配的擴充套件,不需要進行復雜的程式配置。

(2)在設計層面上來說,為什麼要使用MEF?為了“鬆耦合”!我們知道,程式設計有幾個原則,“高內聚,低耦合”就是其中一個。使用MEF可以幫助我們減少內庫之間的耦合。

當然,如果你之前壓根都沒有聽說過MEF,那麼即使看了我上面的解釋,估計也還是雲裡霧裡。沒關係,如果此刻你還有興趣,看了下面的Demo,相信你會有一個初步的認識。

2、為什麼要使用MEF:上面已經解釋過,為了程式的擴充套件和“鬆耦合”。

3、MEF的使用:

(1)MEF基礎匯入匯出的使用:

MEF的使用步驟主要分三步:宿主MEF並組合部件、標記物件的匯出、物件的匯入使用。

我們先來看一個Demo。

   class Program2
    {
     //匯入物件使用 [Import(
"chinese_hello")] public Person oPerson { set; get; } static void Main(string[] args) {
var oProgram = new Program2(); oProgram.MyComposePart(); var strRes = oProgram.oPerson.SayHello("李磊"); Console.WriteLine(strRes); Console.Read(); }
     //宿主MEF並組合部件
void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } }   public interface Person { string SayHello(string name); }
   //宣告物件可以匯出 [Export(
"chinese_hello", typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } [Export("american_hello", typeof(Person))] public class American : Person { public string SayHello(string name) { return "Hello:" + name ; } }

得到結果:

我們來分析下這段程式碼:

     //宿主MEF並組合部件
        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //將部件(part)和宿主程式新增到組合容器
            container.ComposeParts(this);
        }

這個方法表示添加當前Program2這個類到組合容器,為什麼要新增到組合容器?是因為只要新增到組合容器中之後,如果該類裡面有Import,MEF才會自動去尋找對應的Export。這也就是為什麼使用MEF前必須要組合部件的原因。

    [Export("chinese_hello", typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

這裡的[Export("chinese_hello", typeof(Person))]這個特性表示標記Chinese類的匯出。

將Export轉到定義可以看到:

        //
        // 摘要: 
        //     通過在指定協定名稱下匯出指定型別,初始化 System.ComponentModel.Composition.ExportAttribute 類的新例項。
        //
        // 引數: 
        //   contractName:
        //     用於匯出使用此特性標記的型別或成員的協定名稱,或 null 或空字串 ("") 以使用預設協定名稱。
        //
        //   contractType:
        //     要匯出的型別。
        public ExportAttribute(string contractName, Type contractType);

這裡的兩個引數:第一個表示協定名稱,如果找到名稱相同的Import,那麼就對應當前的Chinese物件;第二個引數表示要匯出的型別。

[Import("chinese_hello")]
 public Person oPerson { set; get; }

這裡的chinese_hello是和Export裡面的chinese_hello對應的,由此可知,每一個[Import("chinese_hello")]這種Import一定可以找到一個對應的Export,如果找不到,程式就會報異常。當然如果這裡的Import如果改成[Import("american_hello")],那麼oPerson肯定就對應一個American物件。

通過上面的程式可以知道,我們使用[Import]這個特性,它的底層其實就是給我們初始化了一個物件。例如上面的[Import("chinese_hello")]等價於Person oPerson=new Chinese();。看到這裡可能有人就會說這個Import是多此一舉了,既然我們可以new,為什麼非要用這種奇怪的語法呢,怪彆扭的。其實如果我們站在架構的層面,它的好處就是可以減少dll之間的引用。這個留在下一篇來講。

(2)MEF匯入匯出擴充套件:

按照MEF的約定,任何一個類或者是介面的實現都可以通過[System.ComponentModel.Composition.Export] 屬性將其他定義組合部件(Composable Parts),在任何需要匯入組合部件的地方都可以通過在特定的組合部件物件屬性上使用[System.ComponentModel.Composition.Import ]實現部件的組合,兩者之間通過契約(Contracts)進行通訊。通過上面的例子我們可以知道,物件是可以通過Import和Export來實現匯入和匯出的,那麼我們進一步擴充套件,物件的屬性、欄位、方法、事件等是否也可以通過[ImportAttribute]進行匯入呢?

   class Program2
    {
        [Import("TestProperty")]
        public string ConsoleTest { get; set; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            Console.WriteLine(oProgram.ConsoleTest);
            
            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //將部件(part)和宿主程式新增到組合容器
            container.ComposeParts(this);
        }
    }

    public class TestPropertyImport
    {
        [Export("TestProperty")]
        public string TestMmport { get { return "測試屬性可以匯入匯出"; } }
    }

得到結果:

由此說明,屬性也是可以匯入匯出的。原理與上類似。既然屬性可以,那麼欄位就不用演示了,它和屬性應該是類似的。

下面來看看方法是否可以呢?

class Program2
    {
        [Import("chinese_hello")]
        public Person oPerson { set; get; }

        [Import("TestProperty")]
        public string ConsoleTest { get; set; }

        [Import("helloname")]
        public Action<string> TestFuncImport { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();
            oProgram.TestFuncImport("Jim");

//Console.WriteLine(oProgram.ConsoleTest); //var strRes = oProgram.oPerson.SayHello("李磊"); //Console.WriteLine(strRes); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } } public class TestPropertyImport { [Export("TestProperty")] public string TestMmport { get { return "測試屬性可以匯入匯出"; } } [Export("helloname", typeof(Action<string>))] public void GetHelloName(string name) { Console.WriteLine("Hello:" + name); } }

由此可知,方法的匯入和匯出是通過匿名委託的方式實現的,那麼由此類推,事件應該也是可以的,有興趣的朋友可以一試。原理和上面是一樣一樣的。

既然屬性、欄位、方法、事件都可以通過Import和Export實現單一物件或變數的匯入和匯出,那麼如果我們想要一次匯入多個物件呢?嘿嘿,微軟總是體貼的,它什麼都為我們考慮到了。我們來看看如何實現。

class Program2
    {
        [ImportMany]
        public IEnumerable<Person> lstPerson { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            Console.WriteLine(oProgram.lstPerson.Count());

            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //將部件(part)和宿主程式新增到組合容器
            container.ComposeParts(this);
        }
    }

public interface Person
    {
        string SayHello(string name);
    }

    [Export(typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export(typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }

得到的結果為2。這裡有一點需要注意的,使用ImportMany的時候對應的Export不能有chinese_hello這類string引數,否則lstPerson的Count()為0.

(3)MEF的延遲載入

我們知道,當裝配一個元件的時候,當前元件裡面的所有的Import的變數都自動去找到對應的Export而執行了例項化,有些時候,出於程式效率的考慮,不需要立即例項化物件,而是在使用的時候才對它進行例項化。MEF裡面也有這種延遲載入的機制。

class Program2
    {
        [Import("chinese_hello")]
        public Person oPerson { set; get; }

        [Import("american_hello")]
        public Lazy<Person> oPerson2 { set; get; }

     static void Main(string[] args) { var oProgram = new Program2(); oProgram.MyComposePart(); var strRes = oProgram.oPerson.SayHello("李磊"); var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei"); Console.WriteLine(strRes); Console.Read(); } void MyComposePart() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(catalog); //將部件(part)和宿主程式新增到組合容器 container.ComposeParts(this); } }    public interface Person { string SayHello(string name); } [Export("chinese_hello", typeof(Person))] public class Chinese : Person { public string SayHello(string name) { return "你好:" + name ; } } [Export("american_hello", typeof(Person))] public class American : Person { public string SayHello(string name) { return "Hello:" + name ; } }

通過除錯可知,當程式執行到var strRes = oProgram.oPerson.SayHello("李磊");這一行的時候

oPerson物件已經例項化了,而oPerson2.Value物件沒有例項化,當程式執行var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei")這一句的時候,oPerson2.Value物件才進行例項化。這種需要在某些對程式效能有特殊要求的情況下面有一定的作用。

講到這裡,我們再來看前面關於理解MEF的兩個關鍵點:

(1)可擴充套件的庫:由於MEF允許通過Import的方式直接匯入物件、屬性、方法等,試想,有人開發了一個元件,他們事先定義好了一系列的匯出(Export),我們只需要將它的元件引進來,使用Import的方式按照他們Export的約定匯入物件即可,不用做其他複雜的配置。

(2)能更好的實現“鬆耦合”:比如我們專案按照面向介面程式設計的方式這樣分層:UI層、BLL介面層、BLL實現層......UI層只需要引用BLL介面層即可,我們在BLL實現層裡面定義好Export的匯出規則,然後再UI層裡面使用Import匯入BLL實現層的物件即可,這樣UI層就不需要新增BLL實現層的引用。減少了dll之間的依賴。

以上就是MEF的一些基礎用法。當然在實際使用中可能不會這麼簡單,但是再複雜的用法都是在這些簡單基礎上面擴充套件起來的。後面還有兩篇會繼續分享MEF在專案設計層面的用法以及帶來的好處。歡迎各位拍磚斧正~~

相關推薦

C#系列——MEF實現設計的“耦合

前言:前篇 C#進階系列——MEF實現設計上的“鬆耦合”(一) 介紹了下MEF的基礎用法,讓我們對MEF有了一個抽象的認識。當然MEF的用法可能不限於此,比如MEF的目錄服務、目錄篩選、重組部件等高階應用在這裡就不做過多講解,因為博主覺得這些用法只有在某些特定的環境下面才會用到,著實不太普遍,感覺沒有鑽下去的

C#系列——MEF實現設計的“耦合

前言:最近去了趟外地出差,介紹推廣小組開發的框架類產品。推廣物件是本部門在專案上面的同事——1到2年工作經驗的初級程式設計師。在給他們介紹框架時發現很多框架設計層面的知識他們都沒有接觸過,甚至沒聽說過,這下囧了~~於是乎在想該如何跟他們解釋MEF、AOP、倉儲模式等方面的東東。本來 C#基礎系列 應該還有兩篇

C#系列——MEF實現設計的“耦合:建構函式注入

前言:今天十一長假的第一天,本因出去走走,奈何博主最大的樂趣是假期坐在電腦前看各處堵車,順便寫寫部落格,有點收穫也是好的。關於MEF的知識,之前已經分享過三篇,為什麼有今天這篇?是因為昨天分享領域服務的時候,用到MEF的注入有參建構函式的方法,博主好奇心重,打算稍微深挖一下,這篇來對此知識點做個總結。 還是

C#系列——MEF實現設計的“耦合終結篇:面向介面程式設計

序:忙碌多事的八月帶著些許的倦意早已步入尾聲,金秋九月承載著抗戰勝利70週年的喜慶撲面而來。沒來得及任何準備,似乎也不需要任何準備,因為生活不需要太多將來時。每天忙著上班、加班、白加班,忘了去憤,忘了去算計所謂的價值。天津爆炸事故時刻警示著我們生命的無常,逝者安息,活著的人生活還得繼續,珍惜生命,遠離傷害。武

C#系列——DDD領域驅動設計初探:倉儲Repository

前言:上篇介紹了DDD設計Demo裡面的聚合劃分以及實體和聚合根的設計,這章繼續來說說DDD裡面最具爭議的話題之一的倉儲Repository,為什麼Repository會有這麼大的爭議,博主認為主要原因無非以下兩點:一是Repository的真實意圖沒有理解清楚,導致設計的紊亂,隨著專案的橫向和縱向擴充套件,

【9】C++系列泛型設計以及STL標準模板庫

1、泛型程式設計基本概念 泛型程式設計:編寫不依賴與具體資料型別的程式,將演算法從特定的資料結構中抽象出來,成為通用的。C++的模板為泛型程式設計定義了關鍵的基礎。 兩個術語:概念,模型 概念:用來界定具備一定功能的資料型別,例如:將“可以比較大小的所有資料型別(有比較

C#系列——DDD領域驅動設計初探:領域服務

前言:之前一直在搭建專案架構的程式碼,有點偏離我們的主題(DDD)了,這篇我們繼續來聊聊DDD裡面另一個比較重要的知識點:領域服務。關於領域服務的使用,書中也介紹得比較晦澀,在此就根據博主自己的理解談談這個知識點的使用。 DDD領域驅動設計初探系列文章: 一、領域服務的引入 在《領域驅動設計:軟體核

C#系列——DDD領域驅動設計初探:WCF搭建

前言:前面三篇分享了下DDD裡面的兩個主要特性:聚合和倉儲。領域層的搭建基本完成,當然還涉及到領域事件和領域服務的部分,後面再專案搭建的過程中慢慢引入,博主的思路是先將整個架構走通,然後一步一步來新增相關元素,使架構慢慢變得豐滿。這篇打算分享下應用層的搭建。根據DDD的設計原則,應用層不包含任何領域邏輯,它主

C#系列——DDD領域驅動設計初探:AutoMapper使用

前言:前篇搭建了下WCF的程式碼,就提到了DTO的概念,對於為什麼要有這麼一個DTO的物件,上章可能對於這點不太詳盡,在此不厭其煩再來提提它的作用: 從安全上面考慮,領域Model都帶有領域業務,讓Client端引用Domain Model就意味著Client端可以繞過應用層直接完成業務邏輯的呼叫,這樣

C#系列——DDD領域驅動設計初探:倉儲Repository

前言:上篇介紹了下倉儲的程式碼架構示例以及簡單分析了倉儲了使用優勢。本章還是繼續來完善下倉儲的設計。上章說了,倉儲的最主要作用的分離領域層和具體的技術架構,使得領域層更加專注領域邏輯。那麼涉及到具體的實現的時候我們應該怎麼做呢,本章就來說說倉儲裡面具體細節方便的知識。 DDD領域驅動設計初探系列文章:

C#系列——DDD領域驅動設計初探:Web層的搭建

前言:好久沒更新部落格了,每天被該死的業務纏身,今天正好一個模組完成了,繼續來完善我們的程式碼。之前的六篇完成了領域層、應用層、以及基礎結構層的部分程式碼,這篇打算搭建下UI層的程式碼。 DDD領域驅動設計初探系列文章: 一、UI層介紹 在DDD裡面,UI層的設計也分為BS和CS,本篇還是以Web為

C#系列——DDD領域驅動設計初探:聚合

前言:又有差不多半個月沒寫點什麼了,感覺這樣很對不起自己似的。今天看到一篇博文裡面寫道:越是忙人越有時間寫部落格。呵呵,似乎有點道理,博主為了證明自己也是忙人,這不就來學習下DDD這麼一個聽上去高大上的東西。前面介紹了下MEF和AOP的相關知識,後面打算分享Automapper、倉儲模式、WCF等東西的,可是

C#系列——WebApi 異常處理解決方案

機制 輸出 ges 如果 但是 rom lba slist 解決 出處:http://www.cnblogs.com/landeanfen/p/5363846.html 閱讀目錄 一、使用異常篩選器捕獲所有異常 二、HttpResponseException自

C#系列——WebApi 接口測試工具:WebApiTestClient

spa type 區域 all 手動 shee 找到 網絡 打開文件 C#進階系列——WebApi 接口測試工具:WebApiTestClient 前言:這兩天在整WebApi的服務,由於調用方是Android客戶端,Android開發人員也不懂C#語法,API裏

C#系列——WebApi 路由機制剖析:你準備好了嗎?

事先 blank path can tex 全局配置 dex 找不到 save 前言:從MVC到WebApi,路由機制一直是伴隨著這些技術的一個重要組成部分。 它可以很簡單:如果你僅僅只需要會用一些簡單的路由,如/Home/Index,那麽你只需要配置一個默認路由就能簡

C#系列——WebApi 跨域問題解決方案:CORS

dea ati ice pro target default 異常 測試工具 復雜 前言:上篇總結了下WebApi的接口測試工具的使用,這篇接著來看看WebAPI的另一個常見問題:跨域問題。本篇主要從實例的角度分享下CORS解決跨域問題一些細節。 WebApi系列文章

[轉]C#系列——WebApi 接口返回值不困惑:返回值類型詳解

try 接口測試工具 des rep home creat port 調用 學習 本文轉自:http://www.cnblogs.com/landeanfen/p/5501487.html 閱讀目錄 一、void無返回值 二、IHttpActionResult

C#系列——WebApi 身份認證解決方案:Basic基礎認證

str 常見 bre 這一 dex ace timeout ticket 結合 閱讀目錄 一、為什麽需要身份認證 二、Basic基礎認證的原理解析 1、常見的認證方式 2、Basic基礎認證原理 三、Basic基礎認證的代碼示例 1、登錄過程 2、/Home/I

C#系列——WebApi 接口參數不再困惑:傳參詳解

pub 博客 bapi write ids 簡單 指定 數組 這也 https://www.cnblogs.com/landeanfen/p/5337072.html 閱讀目錄 一、get請求 1、基礎類型參數 2、實體作為參數 3、數組作為參數 4

【8】C++系列過載

1、過載規則 c++幾乎可以過載全部的運算子,而且只能夠過載c++已有的運算子。 其中,不能過載的運算子:"." 、 ".*" 、"::"、"?:" 過載之後運算子的優先順序和結合性都不會改變。 運算子過載是針對新型資料的實際需要,對原有運算子進行適當的改造。例如: 使複數的物件