天天看點

你真的了解Ioc與AOP嗎?(1)

本系列的全部源代碼及二進制檔案可以從這裡下載下傳:IocInCSharp.rar

你真的了解Ioc與AOP嗎?(1)

你真的了解Ioc與AOP嗎?(2)

你真的了解Ioc與AOP嗎?(3)

你真的了解Ioc與AOP嗎?(4)

你真的了解Ioc與AOP嗎?(5)

本部分示例代碼請參考"src/Step1"、"src/Step2"目錄

你真的了解Ioc與AOP嗎?我現在還不是很了解,而且越學習越發現自己了解的很少,Ioc與AOP中蘊涵了大量的能量等待我們去開發。在這個系列中,我僅僅利用Sping.net這個架構向大家展示一下Ioc與AOP的強大功能(呵呵,其實寫這段話的目的是因為“文章題目”牛皮吹得有點大了,給自己個台階下罷了)。

在這個系列中一共包含6個案例,從簡單到複雜,也是對問題分解、思考和解決的一個過程,它們分别是:(1)類之間的依賴;(2)接口依賴;(3)基于配置檔案和Reflection的工廠模式;(4)使用Spring.net實作Ioc;(5)Romoting;(6)利用Ioc在不動一行代碼的情況下實作Remoting。為了更好的了解文中的内容,最好順序閱讀。

作為一個應用系統,代碼複用至關重要。如果在你的設計中,類與類存在很強的互相關聯,那麼你會發現在重用這些元件時就存在很嚴重的問題。在Step1到Step3-Reflection的例子中,我們試圖 利用“針對接口程式設計”以及自己設計的Ioc對系統進行解耦。在Step3到Step5的例子中,我們将利用Spring.net提供的Ioc架構,輕松完成解耦以及系統改造等工作。

一、類之間的依賴

我們的第一個例子主要用于說明程式的基本構造,并且作為一個反面典型,引出為什麼要解耦,以及如何下手。在這個例子中,我們将建立三個程式集,分别是MainApp.exe、HelloGenerator.dll以及SayHello.dll。它們之間的關系如下圖所示:

你真的了解Ioc與AOP嗎?(1)

HelloGenerator類根據提供的姓名産生一個問候字元串,代碼如下:

using System;
namespace IocInCSharp
{
   public class EnHelloGenerator
   {
      public string GetHelloString(string name)
      {
         return String.Format("Hello, {0}", name);
      }
   }
}
      

SayHello類持有一個對EnHelloGenerator的引用,并負責将生成出來的問候字元串列印出來。

using System;
namespace IocInCSharp
{
   public class SayHello
   {
      private EnHelloGenerator _helloGen;
      public EnHelloGenerator HelloGenerator
      {
         get { return _helloGen; }
         set { _helloGen = value; }
      }
      public void SayHelloTo(string name)
      {
         if(_helloGen != null)
            Console.WriteLine(_helloGen.GetHelloString(name));
         else
            Console.WriteLine("Client.hello is not initialized");
      }
   }
}      

MainApp.exe負責完成對象的建立、組裝以及調用工作:

using System;
namespace IocInCSharp
{
   public class MainApp
   {
      public static void Main()
      {
         SayHello sayHello = new SayHello();
         sayHello.HelloGenerator = new EnHelloGenerator();
         sayHello.SayHelloTo("zhenyulu");
      }
   }
}
      

在這個設計中,元件與元件之間、類與類之間存在着緊密的耦合關系。SayHello類中的_helloGen字段類型為EnHelloGenerator,這将導緻我們很難給它賦予一個其它的HelloGenerator(例如CnHelloGenerator,用于生成中文問候語)。另外MainApp也嚴重依賴于SayHello.dll以及HelloGenerator.dll,在程式中我們可以看到類似new SayHello();new EnHelloGenerator();的指令。

這種緊密的耦合關系導緻元件的複用性降低。試想,如果想複用SayHello元件,那麼我們不得不連同HelloGenerator一同拷貝過去,因為SayHello.dll是依賴與HelloGenerator.dll的。解決這個問題的辦法就是“針對抽象(接口)”程式設計 (依賴倒置原則)。這裡的抽象既包括抽象類也包括接口。我不想過多的去談抽象類和接口的差別,在後續的例子中我們将使用接口。由于接口在進行“動态代理”時仍能保持類型資訊,而抽象類可能由于代理的原因導緻繼承關系的“截斷”(如MixIn等)。除此之外,對于單繼承的C#語言而言,使用接口可以擁有更大的彈性。

二、接口依賴

既然類之間的依賴導緻耦合過于緊密,按照《設計模式》的理論,我們要依賴于接口。但是人們往往發現,僅僅依賴于接口似乎并不能完全解決問題。我們從上面的例子中抽象出接口後,元件間的依賴關系可能變成如下圖所示:

你真的了解Ioc與AOP嗎?(1)

經過改造後,SayHello不再依賴于具體的HelloGenerator,而是依賴于IHelloGenerator接口,如此一來,我們可以動态的将EnHelloGenerator或是CnHelloGenerator賦給SayHello,其列印行為也随之發生改變。接口的定義以及改造後的SayHello代碼如下(為了節省空間,将代碼合并書寫):

using System;
namespace IocInCSharp
{
   public interface IHelloGenerator
   {
      string GetHelloString(string name);
   }
   public interface ISayHello
   {
      IHelloGenerator HelloGenerator{ get; set; }
      void SayHelloTo(string name);
   }
   public class SayHello : ISayHello
   {
      private IHelloGenerator _helloGen;
      public IHelloGenerator HelloGenerator
      {
         get { return _helloGen; }
         set { _helloGen = value; }
      }
      public void SayHelloTo(string name)
      {
         if(_helloGen != null)
            Console.WriteLine(_helloGen.GetHelloString(name));
         else
            Console.WriteLine("Client.hello is not initialized");
      }
   }
}
      

但是我們的MainApp似乎并沒有從接口抽象中得到什麼好處,從圖中看,MainApp居然依賴于三個元件:ICommon.dll、HelloGenerator.dll以及SayHello.dll。這是由于MainApp在這裡負責整體的“裝配”工作。如果這三個元件中的任何一個發生變化,都将導緻MainApp.exe的重新編譯和部署。從這個角度來看,似乎“針對接口程式設計”并沒有為我們帶來太多的好處。

如果能夠将“元件裝配”工作抽象出來,我們就可以将MainApp的複雜依賴關系加以簡化,進而 進一步實作解耦。為此,我們引入“工廠”模式,并利用配置檔案和反射技術,動态加載和裝配相關元件。(待續)