摘要:[.NET] 實作工廠模式、 IoC (Inversion of Control) 與 DI (Depedency Injection)
前言
嗯..覺得之前的敘述不是很好,重新在敘述看看.... : (
工廠模式是Design Pattern裡的一種設計模式,讓不同Client透過同一個Factory入口作它需要的工作,而IoC 全名 Inversion of Control,中文稱 控制反轉,IoC 是設計模式的一種,主要用來降低偶合性並且能夠抽換工作類別,因為當程式開發到一定的規模時,程式偶合性越高之後的維護就越困難,並且會造成動了一個地方的程式卻要更改一堆連帶的程式碼問題。
感覺上使用工廠模式跟IoC兩個其實蠻相似的,都是使用統一的入口並且能夠抽換要實作的物件,但是在IoC的使用上是比工廠模式還要進階的使用,因為IoC可以透過依賴注入(DI)達到更靈活的使用,接下來舉個養寵物的例子來說明其中的差異。
範例
我養了一隻狗並且牠會有以下幾種行為,
- Eat : 吃飯
- Sleep : 睡較
- Walk : 走路
以Class Diagram來看的話就如同下圖:
在一般的情況下,我們會這樣撰寫程式碼
public class Arvin
{
public void Play()
{
CallMyDog();
}
private void CallMyDog()
{
Dog dog = new Dog();
Console.WriteLine("主人餵食了");
dog.Eat();
Console.WriteLine("主人帶去散步");
dog.Walk();
Console.WriteLine("主人說要睡覺了");
dog.Sleep();
}
}
結果:
以上例的情況下來看,當我只養了一隻狗的時候其實是沒有什麼問題的,但是,如果女朋友說: 寶貝~我又想養一隻貓耶,我要養~ >.^ 咪揪 ,這時後會碰到什麼問題呢?
如果還是以一般的想法下去處理的話會這樣作,Class Diagram如下:
並且以下方這種方式撰寫程式碼,
public class Arvin
{
public void Play()
{
CallMyDog();
Console.WriteLine("");
CallMyCat();
}
private void CallMyDog()
{
Dog dog = new Dog();
Console.WriteLine("主人餵食了");
dog.Eat();
Console.WriteLine("主人帶去散步");
dog.Walk();
Console.WriteLine("主人說要睡覺了");
dog.Sleep();
}
private void CallMyCat()
{
Cat cat = new Cat();
Console.WriteLine("主人餵食了");
cat.Eat();
Console.WriteLine("主人帶去散步");
cat.Walk();
Console.WriteLine("主人說要睡覺了");
cat.Sleep();
}
}
結果如下:
在以上的情況下,聰明的你應該已經可以發現,因為養的寵物的增加導致必須要一直去修改Arvin Class增加CallMyXXX Method程式碼,最後將變得又臭又長難以維護,當然這樣的作法其實是很 不科學 的,所以我們必須要找個聰明的方法來處理日後因為寵物增加而帶來的問題,首先,我先使用Design Pattern的工廠模式來實作試試,我將類別稍作調整,將所有的寵物都歸納成動物(Animal)因為它們都有相同的行為模式,建立IAnimal介面讓底下的寵物都實作此Interface,接著建立一個統一入口AnimalFactory類別並且包含一個CreatePet方法,CreatePet方法會依照需求產生對應的實體物件,Class Diagram如下:
public interface IAnimal
{
string Name { get; set; }
void Eat();
void Sleep();
void Walk();
}
public class AnimalFactory
{
public enum AnimalType
{
Dog,
Cat
}
public static IAnimal CreatePet(AnimalType pAnimalType)
{
switch (pAnimalType)
{
case AnimalType.Dog:
return new Dog();
case AnimalType.Cat:
return new Cat();
default:
return null;
}
}
}
AnimalFactory 會根據傳入的 AnimalType 參數決定要產生哪個對應的物件,而我的 Arvin Class 就可以修改成呼叫單一方法CallMyPet(AnimalFactory.AnimalType),而不用像之前的作法一樣需要持續增加對應的寵物方法。
public void Play()
{
CallMyPet(AnimalFactory.AnimalType.Dog);
Console.WriteLine("");
CallMyPet(AnimalFactory.AnimalType.Cat);
}
private void CallMyPet(AnimalFactory.AnimalType pAnimalType)
{
var factory = AnimalFactory.CreatePet(pAnimalType);
Console.WriteLine("主人餵食了");
factory.Eat();
Console.WriteLine("主人帶去散步");
factory.Walk();
Console.WriteLine("主人說要睡覺了");
factory.Sleep();
}
以上工廠模式可以在當要增加新的寵物時,只需要增加對應的 Class(例如: Bird Class) 並且修改AnimailFactory的swicth case即可,但是可能還是有人會說「可是這樣我還是需要修改程式呀」 ,的確在這種作法下當增加新類別時還是需要小調整程式碼,所以在這邊就可以換使用 IoC 的設計模式,使用 DI 來實作 IoC 吧 !
依賴注入(DI)是IoC的實現方法,於服務進行中根據服務的環境透過IoC容器將要實例化的物件注入運行,DI分三種方式注入方式各為接口注入(Interface Injection)、構造器注入(Constructor Injection)、屬性注入(Setter Injection),在.NET中一般會透過組態檔配上反射機制來做,接下來看一下該如何做,第一步需要在web.config檔中建立一塊將要使用的configSections,如何建立自定組態區段的方法可以參考我 使用 configSections 自訂組態區段 這篇文章,這裡就不敘述了,組態檔的設定如下:
<configuration>
<configSections>
<section name="AnimailType" type="TIocAndDI.Configuration.TIocAndDIConfigurationSection, TIocAndDI"/>
</configSections>
<AnimailType>
<typeAliases>
<add name="Dog" type="TIocAndDI.interface.IAnimal, TIocAndDI" />
<add name="Cat" type="TIocAndDI.interface.IAnimal, TIocAndDI" />
</typeAliases>
</AnimailType>
</configuration>
接下來調整 Arvin Class 的方法如下:
public class Arvin
{
public void Play()
{
CallMyPet();
}
private void CallMyPet()
{
Configuration.TIocAndDIConfigurationSection clientConfiguration = ConfigurationManager.GetSection("AnimailType") as
Configuration.TIocAndDIConfigurationSection;
IEnumerator configurationReader = clientConfiguration.TypeAliasesClientConfigurations.GetEnumerator();
while (configurationReader.MoveNext())
{
if (configurationReader.Current is Configuration.TypeAliasesConfigurationElement)
{
Configuration.TypeAliasesConfigurationElement clientConfigurationElement = configurationReader.Current as
Configuration.TypeAliasesConfigurationElement;
var factory = AnimalFactory.CreatePet(clientConfigurationElement.Name);
Console.WriteLine("主人餵食了");
factory.Eat();
Console.WriteLine("主人帶去散步");
factory.Walk();
Console.WriteLine("主人說要睡覺了");
factory.Sleep();
Console.WriteLine("");
}
}
}
}
調整AnimalFactory Class的方法如下:
public class AnimalFactory
{
public static IAnimal CreatePet(string pAnimalName)
{
Type oType = Type.GetType(pAnimalName);
IAnimal animal = (IAnimal)Activator.CreateInstance(oType);
return animal;
}
}
透過自訂組態區段取得的值來呼叫AnimalFactory.CreatePet方法,於AnimalFactory Class中取得Type並建立實體後回傳並執行方法,當然可以把產生的實體改為在Dog, Cat這些物件本身自己處理產生更好,這樣Factory將更為單純。
所以在IoC的設計架構下可以發現系統更加靈活,只要抽換組態檔內的項目就可以依照需求執行工作了,以上為使用心得備記,不知道重述有沒有比較清楚,如有錯誤請多指教,謝謝。
----------------------------補充線--------------------------
91大於回應中有提到可以使用foreach取得組態檔資料,我在這邊也補充一下該如何使用foreach取得組態檔,程式碼如下:
Configuration.TIocAndDIConfigurationSection
clientConfiguration = ConfigurationManager.GetSection("AnimailType") as
Configuration.TIocAndDIConfigurationSection;
foreach (Configuration.TypeAliasesConfigurationElement configElement in
clientConfiguration.TypeAliasesClientConfigurations)
{
var factory = AnimalFactory.CreatePet(configElement.Name);
Console.WriteLine("主人餵食了");
factory.Eat();
Console.WriteLine("主人帶去散步");
factory.Walk();
Console.WriteLine("主人說要睡覺了");
factory.Sleep();
Console.WriteLine("");
}
範例程式碼
參考資料
[分享] 什麼是 Inversion Of Control?
[ASP.NET]重構之路系列v4 – 簡單使用interface之『你也會IoC』
以上文章敘述如有錯誤及觀念不正確,請不吝嗇指教
如有侵權內容也請您與我反應~謝謝您 :)