IOC前世今生
前些天,參與了公司內(nèi)部小組的一次技術(shù)交流,主要是針對《IOC與AOP》,本著學(xué)而時習(xí)之的態(tài)度及積極分享的精神,我就結(jié)合一個小故事來初淺地剖析一下我眼中的“IOC前世今生”,以方便初學(xué)者能更直觀的來學(xué)習(xí)與理解IOC!也作拋磚引玉之用。
(雖說故事中的需求有點小,但看客可在腦海中盡量把他放大,想象成一個很大的應(yīng)用系統(tǒng))
一、IOC雛形
1、程序V1.0
??? 話說,多年以前UT公司提出一個需求,要提供一個系統(tǒng),其中有個功能可以在新春佳節(jié)之際給公司員工發(fā)送一封郵件。郵件中給大家以新春祝福,并告知發(fā)放一定數(shù)額的過節(jié)費。
???? 經(jīng)分析,決定由張三、李四和王五來負(fù)責(zé)此系統(tǒng)的開發(fā)。
??? 其中:由張三負(fù)責(zé)業(yè)邏輯控制模塊 LogicController的開發(fā),此處簡化為UT.LogicController.exe ;由李四負(fù)責(zé)祝福消息管理類(GreetMessageService),并集成到組件 UT.MessageService.dll中;由王五負(fù)責(zé)郵件功能幫助類(EmailHelper),并提供組件 UT.Email.dll。
???? 類依賴關(guān)系如下:
??? 王五郵件功能模塊核心代碼如下:?
public class EmailHelper {public void Send(string message){Console.Write("Frome email: " + message); } }? 李四消息管理模塊核心代碼如下:
public class GreetMessageService {EmailHelper greetTool;public GreetMessageService(){greetTool = new EmailHelper();}public void Greet(string message){greetTool.Send(message);} }??張三業(yè)務(wù)集成模塊核心代碼如下:
string message = "新年快樂!過節(jié)費5000."; MessageService.GreetMessageService service = new MessageService.GreetMessageService(); service.Greet(message);?三人經(jīng)過一個月的艱苦奮戰(zhàn),終于大功告成,系統(tǒng)也在春節(jié)其間成功發(fā)出問候信。企業(yè)如此關(guān)懷,給員工帶來無比的溫暖,因此深受全體員工好評!
???? 春節(jié)過后,相應(yīng)的功能也移植到了與“UT公司”相關(guān)的“UT編輯部”和“UT房產(chǎn)”類似的應(yīng)用當(dāng)中,并在后繼的“元宵”、“端午”、“中秋”等節(jié)日中得以廣泛應(yīng)用。
2、程序V2.0
??? 又是一個年關(guān)將至……
??? 說真的,過節(jié)費的多少,有時可能直接影響整個假日的行程安排、從而影響假日的整體質(zhì)量,因此部門領(lǐng)導(dǎo)高度重視。而郵件通知的方式,在邊遠(yuǎn)山區(qū)常常因為受網(wǎng)絡(luò)環(huán)境的影響而無法正常收取,許多在外過年的同事對此頗有微詞。后經(jīng)多方考證,決得采用當(dāng)下非常主流的電話語言播報的方式進行通知。
??? 于是乎,張三、李四、王五又忙起來了。但李四,卻有點頭疼了,因為他的模塊現(xiàn)在不僅在“UT公司”內(nèi)部使用,而且還在“UT編輯部”和“UT房產(chǎn)”也都有獨立運行。如何讓此處變化影響最小,就得費點腦筋。為了達到較好的效果,李四決定按以下方式進行整改。
??? ①、初始設(shè)計方案如下:
??? 首先為了能讓不同“祝福方式”能有效替換,決定以“面向接口”的方式來進行分離。同時,讓EmailHelper的郵件通知類和TelephoneHelper的語音播報類都實現(xiàn)此接口。核心代碼如下:
public interface ISendable {void Send(string message); }public class EmailHelper : ISendable {public void Send(string message){Console.Write("Frome email: " + message);} }public class TelephoneHelper : ISendable {public void Send(string message){Console.Write("Frome telephone: " + message);} }?再者,為了方便兼容新舊產(chǎn)品,要求Controller決定當(dāng)前采用什么方式進行通信,并以參數(shù)方式傳給消息管理模塊,核心代碼如下:
public enum SendToolType {Email,Telephone, }【備注】:上述代碼,并不是一個優(yōu)秀的設(shè)計,在后繼的優(yōu)化方案當(dāng)中將被去除。
public class GreetMessageService {ISendable greetTool;public GreetMessageService(SendToolType sendToolType){if (sendToolType == SendToolType.Email){greetTool = new UT.EmailV20.EmailHelper();}else if (sendToolType == SendToolType.Telephone){greetTool = new UT.TelephoneV20.TelephoneHelper();}}public void Greet(string message){greetTool.Send(message);} }?最后,業(yè)務(wù)集成模塊結(jié)合具體業(yè)務(wù)需求進行適當(dāng)?shù)恼{(diào)整,核心代碼如下:?
?
string message = "新年快樂!過節(jié)費5000."; GreetMessageService service = new GreetMessageService(SendTool.Telephone); service.Greet(message);???? 眼看即將完工,但李四卻越看越不順眼,因為考慮到以后可能再添加新的祝福方式,這種未來的不確定性,一定會讓李四現(xiàn)有的枚舉SendToolType和 GreetMessageService中的構(gòu)造函數(shù)不斷的進行更改,這將會是一個沒完沒了工作。
???? 再說了,既然張三要傳SendToolType給我,也就是說在具體產(chǎn)品應(yīng)用時,張三的模塊肯定是知道要采用什么方式進行祝福,那么何不讓他直接把祝福方式的實例而不是簡單的方式類型給我呢?這樣,我不就省事了嗎,于是乎把設(shè)計進行了優(yōu)化。
???? ②、優(yōu)化后設(shè)計方案:
?
? ?
??? 又是一個月的苦戰(zhàn)……
??? 王五的代碼不受影響。
??? 李四刪除 SendToolType枚舉,同進把GreetMessageService改成如下:
public class GreetMessageService {ISendable greetTool;public GreetMessageService(ISendable sendtool){greetTool = sendtool;}public void Greet(string message){greetTool.Send(message);} }?張三,也把業(yè)務(wù)邏輯控制部分改成如下:
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new TelephoneHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
最終:張三更新UT.LogicController.exe中的實現(xiàn);李四更新了UT.MessageSevice.dll,王五提供新的組件:UT.Telephone.dll,并把接口集成到一個叫UT.Core.dll的庫中。經(jīng)多方集成測試后系統(tǒng)運行良好!
????【點評】:
????李四此處成功的利用“接口分離”、并結(jié)合“依賴倒置”的方式,使得自己負(fù)責(zé)的模塊初步具備了應(yīng)對新增祝福方式的擴展要求。同時由于其采用的“依賴注入”方式要求李四的業(yè)務(wù)邏輯控制模塊對其所需的?“ISendable”實例進行注入,理論上已經(jīng)初步具體了“IOC反轉(zhuǎn)控制”的雛形。
??? 對“IOC反轉(zhuǎn)控制”此時帶來的優(yōu)勢就是:確保了“紅色框”內(nèi)的模塊是具有應(yīng)對變化的能力,在后繼新增新祝福方式時,UT.MessageService.dll組件可以完全不做任何修改。
?
3、V2.1
??? 由于電話語言播報必須接聽、過后不便留底查詢等不足也常被人們詬病,因此短信通知的方式被提上議程。
??? 在此要求下,王五提供了新的組件:UT.GSN.dll。核心代碼如下:
public class SMSHelper : ISendable {public void Send(string message){Console.WriteLine("Frome SMS: " + message);} }
?張三也把代碼改成了如下,
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new SMSHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);??? 李四坐享其成!
?
4、V2.2
??? 祝福方式日新月異人們的要求也是不斷發(fā)展,沒過多久短信方式太呆板、信息量不足等缺陷也暴露出來,微信深受大伙青睞。
??? 在此要求下,王五提供了新的組件:UT.Wechat.dll。核心代碼如下:
public class WechatHelper : ISendable {public void Send(string message){Console.WriteLine("Frome wechat: " + message);} }?張三也把代碼改成了如下:?
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new WechatHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);
李四再次坐享其成!!
????????
二、IOC擴展
1、李四的逍遙自在與張三的焦頭爛額
??? ?由于采用了IOC反轉(zhuǎn)控制的思想,現(xiàn)在不管系統(tǒng)如何變化,李四負(fù)責(zé)的模塊總的來說還是相當(dāng)穩(wěn)定,因此這些年李四過的可謂逍遙自在。然而,相比之下張三卻因為產(chǎn)品在UT公司、UT編輯部、UT房產(chǎn)等都有獨立應(yīng)用,且各自使用的版本又不盡相同,因此要同時維護三個版本,可謂是焦頭爛額。
??? 當(dāng)然張三曾經(jīng)也想統(tǒng)一各個版本,從而實現(xiàn)代碼的統(tǒng)一維護。為此還專門與各相關(guān)主管溝通過、協(xié)調(diào)過,然而由為UT編輯部與電信服務(wù)商早有合作所有短信免費,因此短信方式最得人心;而UT房產(chǎn)基于對信息接收者身份的特殊性考慮,郵件通知被認(rèn)為是不二選擇。因此,張三統(tǒng)一版本的夢想最終還是無果而終。
???? 我們來看看此時的張三同時維護著三個系統(tǒng),其中各自核心代碼基本如下:
??? UT公司(微信方式)
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new WechatHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);??? UT編輯部(短信方式)?
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new SMSHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);???? UT房產(chǎn)(郵件方式)
string message = "新年快樂! 過節(jié)費5000."; ISendable greetTool = new EmailHelper(); GreetMessageService service = new GreetMessageService(greetTool); service.Greet(message);??? 這些年,本著對工作和客戶的認(rèn)真負(fù)責(zé),張三長時間在這些“版本維護”、“產(chǎn)品兼容”等臟活累活中摸爬滾打,現(xiàn)在是心力憔悴……
?
2、張三的出路
??? 某日張三與李四觥籌交錯、把酒言歡……
??? 酒過三巡,張三對李四說:當(dāng)年你的模塊因“IOC反轉(zhuǎn)控制”而脫身,卻把“變化點”反轉(zhuǎn)到我模塊,由我來生成特定的對象,然后再向你注入。這樣你是輕松了,但我卻深陷泥潭……
?
??? 面對張三的吐槽,李四只能給張三進行細(xì)心分析:
??? 首先、MessageService消息管理模塊作為一個消息專用服務(wù),其實對“是采用郵件還是微信方式進行祝福”這樣的功能性把控本身是不具主動權(quán),由這個模塊來負(fù)責(zé)實在是有點鞭長莫及,即便強扭到一起,這瓜也鐵定甜不了。
??? 還有,本著單一職責(zé)的原則本消息服務(wù)其實是不方便過多地去處理本應(yīng)該是業(yè)務(wù)邏輯處理的類似“選擇祝福方式”這種事情。理論上,作為業(yè)務(wù)集成方的“LogicController”負(fù)責(zé)處理這類業(yè)務(wù)應(yīng)該是責(zé)無傍代。
??? 再者,作為新增需求,王五為此而新增組件(dll)那是必不可少;張三作為業(yè)務(wù)的總集成方也是難以脫身;由于新增需求而引起的變化,對張三和王五產(chǎn)生影響也是情理之中。即便退一萬步來說,就算沒有“反轉(zhuǎn)控制”張三也是要面對變化的(就像V2.0初始方案中的傳入SendToolType參數(shù)),因此有無“反轉(zhuǎn)控制”對張三而言該變的始終還是要變化。那么現(xiàn)在采用“IOC反轉(zhuǎn)控制”而成全了李四的穩(wěn)定,對張三來說這是個“利人不損已”的買賣。
??? 最后,不管從架構(gòu)設(shè)計還是開發(fā)效率上來說,“IOC反轉(zhuǎn)控制”雖說把變化點從李四的“MessageService”模塊反轉(zhuǎn)到了張三的“LogicController”模塊當(dāng)中,但這符合“SOLID面向?qū)ο笤O(shè)計”的原則,可以說是一個好的設(shè)計,本無可厚非!
??? 聽完李四的論述,張三覺得甚是有理,酒不免醒了三分!由于兩人都是這個行業(yè)打拼多年的老鳥,爭論也是點到即止。馬上把交流的重點轉(zhuǎn)移到“如何解決張三同時維護三個產(chǎn)品”的尷尬處境上來。
? ? 經(jīng)過深入分析,兩人覺得要脫困必須解決好如下兩個問題:
??????? ①:如何有效創(chuàng)建“ISendable”實例,減少由于新增祝福方式對實例創(chuàng)建的影響?
??????? ②:如何減少新增祝福方式而對“LogicController”模塊的沖擊,以減少維護成本?
????????
【備注】
???? SOLID面向?qū)ο蟮奈鍌€設(shè)計原則對于開發(fā)人員非常重要,其身影在任何大中型軟件項目中隨處可見,建議必須掌握并靈活應(yīng)用。此五原則分別為:
??? 單一職責(zé)原則(Single Resposibility Principle)
??? 開放封閉原則(Open Closed principle)
??? 里氏替換原則(Liskov Substitution Principle)
??? 接口分離原則(Interface Segregation Principle)
??? 依賴倒置原則?(Dependency Inversion Principle)
?????????
3、解決方案
?? 為了實現(xiàn)“如何有效創(chuàng)建ISendable實例”的問題,張三引入了“工廠模式”,由于不同的祝福方式而產(chǎn)生的變化,封裝在一個獨立的“SendToolFactory”類中,這樣就算以后再有變化,只要更改此類中部分代碼即可,而不影響程序中其他所有用到ISendable的地方。
????【點評】:
?????以工廠模式來實現(xiàn)“ISendable”對象實例的創(chuàng)建,是一種典型的“高內(nèi)聚”與“松耦合”的設(shè)計方式,它有效的使得應(yīng)用程序核心部分并不用去關(guān)心系統(tǒng)到底采用了什么樣的“祝福方式”,而具體的“祝福方式”則在工廠模式內(nèi)部進行創(chuàng)建。如果以后需求有變動,那也只需在工廠做少許修改即可,程序其他代碼都將不受影響。
???? 當(dāng)成功解決完第一個問題后,我們立即拉開針對“如何能實現(xiàn)在新增祝福方式之后,有效的控制對“LogicController”模塊的沖擊”這們問題上來。從目前程序的結(jié)構(gòu)來看,在新增祝福方式之后的主要沖擊有兩方面:首先是更改工廠類中的代碼用以創(chuàng)建新的實例;再者是引入新的動態(tài)庫。
?????最后我們決定采用“工廠模式+反射機制”的方式來解決上述難題,并在工廠模式中依靠配置文件的節(jié)點信息,然后采用“反射機制”來動態(tài)創(chuàng)建相應(yīng)的實例;如此一來,以后就算再有新的祝福方式采用,也只需把王五新增的動態(tài)庫拷貝過來,然后再更改一下配置文件中的節(jié)點信息就行,不再需要更改任何程序源代碼,也不再需要重新編譯生成程序。
?
4、程序V3.0
???? 采用工廠模式創(chuàng)建實例
public abstract class SendToolFactory{public static ISendable GetInstance(){try{Assembly assembly = Assembly.LoadFile(GetAssembly()); // 加載程序集object obj = assembly.CreateInstance(GetObjectType()); // 創(chuàng)建類的實例 return obj as ISendable;}catch{return null;}}static string GetAssembly(){return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["AssemblyString"]); }static string GetObjectType(){return ConfigurationManager.AppSettings["TypeString"];} }配置文件節(jié)點信息
<?xml version="1.0" encoding="utf-8" ?> <configuration><appSettings><!--<add key="AssemblyString" value="UT.EmailV20.dll" /><add key="TypeString" value="UT.EmailV20.EmailHelper" />--><!--<add key="AssemblyString" value="UT.SMSV21.dll" /><add key="TypeString" value="UT.SMSV21.SMSHelper" />--><add key="AssemblyString" value="UT.WechatV22.dll" /><add key="TypeString" value="UT.WechatV22.WechatHelper" /></appSettings> </configuration>
? 自從V3.0推出后,基于“IOC反轉(zhuǎn)控制”的思想也算小有收獲,多年來產(chǎn)品運行良好,就算不斷有新的“祝福方式”出現(xiàn),張三和李四也都不必再為之操心,同時也能適用“UT公司”、“UT編輯部”和“UT房產(chǎn)”等不同的場景要求,可謂皆大歡喜。
????????
【點評】:
????①:IOC反轉(zhuǎn)控制常見的實現(xiàn)手段之一就是DI依賴注入,而依賴注入的方式通常有:接口注入、Setter注入和構(gòu)造函數(shù)注入。本次示例給出的代碼具備“接口注入”的特征,并通過構(gòu)造函數(shù)來實現(xiàn)。
????②:IOC反轉(zhuǎn)控制還有一種手段就是依賴查找,這種方式一般先進行類型注冊,使用時進行查找;對這種方式有興趣的朋友可以參考微軟企業(yè)庫中Microsoft.Practices.Unity.dll中的源碼(https://entlib.codeplex.com/)和詳細(xì)的示例說明整理(如:Enterprise Library 4.1 HOL)。
??? ?③:依賴注入一般由調(diào)用者(LogicController)依賴IOC框架生成好實例對象,然后直接注入到被調(diào)用者(GreetMessageService)當(dāng)中,被者用者內(nèi)部直接使用此實例,代碼流程清晰明了;而依賴查找一般由調(diào)用者(LogicController)前期進行類型注冊,被調(diào)用者(GreetMessageService)內(nèi)部依賴IOC框架獲取到想要的對象實例,然后再使用此實例。
??? ④:兩者生成實例的目的都是為了能動態(tài)創(chuàng)建實例,只不過創(chuàng)建的時機不一樣。我個人認(rèn)為依賴注入分離了邏輯控制相對來說層次性更清晰明了,但在需要注入多個對象時,卻不及查找注入方式方便簡潔。
?
三、IOC框架
1、模式的復(fù)用
???????? 自從張三在上述產(chǎn)品開發(fā)過程中成功地總結(jié)出“IOC思想”后,在后繼的其他產(chǎn)品中進行了推廣與實踐。在使用的過程中,張三發(fā)現(xiàn)這樣的模式是可以很好的在模塊間、產(chǎn)品間進行有效的復(fù)用,不僅大大提高了開發(fā)效率,對產(chǎn)品后繼的擴展和維護都帶來不少方便。
?
2、對象容器
???????? 當(dāng)然,在對“IOC思想”的實踐中,張三還發(fā)現(xiàn)有些地方需要完善。比如,有時我們可能要創(chuàng)建單一對象實例,有時卻要要創(chuàng)建多個對象的實例,甚至有時要創(chuàng)建一系列實例;有時要創(chuàng)建一個本地的對象實例,有時卻要創(chuàng)建一個遠(yuǎn)端的服務(wù)對象實例;等等…..
為了應(yīng)對復(fù)雜的對象應(yīng)用,張三把原來的“對象工廠”這樣的小作坊升級成了一個功能強大的、具有一定智能水平的“IOC對象容器”,這個容器可以動態(tài)的依據(jù)參數(shù)設(shè)定或配置文件來進行有策略性的對象創(chuàng)建與管理,使得整個框架對對象集的管理上升到了一個更高的層次。
?
3、IOC基礎(chǔ)框架
???????? 張三通過前期的“接口分離”及“依賴倒置”達到了“反轉(zhuǎn)控制”的效果,并結(jié)合有效的“依賴注入”方式,實現(xiàn)了系統(tǒng)的“松耦合”架構(gòu);再通過“工廠模式 + 反射機制”有效實現(xiàn)了對象的動態(tài)創(chuàng)建,并在后期升級成“對象容器”,大大減少新增需求對程序帶來的沖擊。通過以上方式,張三成功地摸索出一套行這有效且復(fù)用性高的“IOC基礎(chǔ)框架”。
?
4、IOC思想
??? 后來,張三把摸索總結(jié)出的“IOC基礎(chǔ)框架”在公司各產(chǎn)品中進行了廣泛實踐,得到一致好評,并且被作為一個公共組件集成在一個叫“UT企業(yè)庫”的組件集中。從此,在張三的朋友圈中,IOC思想廣為流傳。
??? 若干年后,我們發(fā)現(xiàn)EJB、Spring、Struts、Asp.netMVC等框架中都能看到IOC思想的影子,這些框架都對張三最初IOC的思想作了進一步的發(fā)揚、光大。
??? 現(xiàn)在,IOC的思想在軟件設(shè)計與系統(tǒng)架構(gòu)中大放異彩,然而非常遺憾中國人口中的那個神秘的張三至今也不知到底是誰。
?
四:源代碼
1、開發(fā)環(huán)境為:VS2010 + NET4.0 + Windos7
2、下載示例源代碼(IOCDemo),代碼很簡單都沒寫注釋。
轉(zhuǎn)載于:https://www.cnblogs.com/caosenianhuan/p/IOC.html
總結(jié)
- 上一篇: 大数据与云计算技术周报(第150期)
- 下一篇: 花卉拍摄技巧 8 — 荷莲