.Net 应用框架设计系列(二)
什么是一個好的設計?我想要做一個好的設計,有這么幾個挑戰(zhàn)。
1。對象的職責的定義和劃分
2。可擴展性
3。可重用性
1.?明確定義和劃分對象的職責。也就是說一個class,他應該專注于做很少的功能,而不是面面具到,無所不能。通過class的名稱和方法,我們可以很清楚這個class到底提供什么樣的功能和職責。說起來很簡單,可是實際做起來,還是很困難的。
2.?可擴展性。我的設計如何才可以做到,在需求發(fā)生變化后,在新的組件和服務產生后,我可以在不改動原有設計而把新的東西集成進來。在我替換和修改了已有組件的實現(xiàn)后,依賴他的上層的代碼不需要做任何的改動。
3.?可重用性。對象之間的依賴關系復雜。如和在一個新的項目里重用以前已經(jīng)實現(xiàn)的class,在我們現(xiàn)在的開發(fā)過程里,通常都是一個復雜的過程。
?
如果解決這三個難題呢。我認為,IOC為我們提供了解決這3個難題的途徑。
IOC,英文是Inversion of Control,中文翻譯為“控制反轉”。用于對調用和被調用者之間進行解耦。
首先我們從解耦這個方面來討論。
對組件之間的關系進行解耦,通常我們需要處理的情況有2種:1。獲取外部的配置信息 2。獲取一個組件對象的引用。舉個例子來說明一下:
using?System.Configuration;
public?class?MyAwfulEmailClass
{
??public?MyAwfulEmailClass()
??{
??}
??
??public?void?SendEmail(?String?from,?String?to,?String?message,?String?templateName?)
??{
????String?host?=?ConfigurationSettings.AppSettings["smtphost"];
????int?port?=?Convert.ToInt(?ConfigurationSettings.AppSettings["smtpport"]?);
????NVelocityTemplateEngine?engine?=?new?NVelocityTemplateEngine();
????String?newMessage?=?engine.Process(?message,?templateName?);
????//?Finally?send?message
??}
}
這是一個很簡單的類,只有寥寥數(shù)行的代碼。也許我們在自己的開發(fā)過程中,會經(jīng)常看到類似的代碼。可是就這寥寥數(shù)行代碼里面,仍然有一些問題存在。
1.?代碼里是通過ConfigurationSettings來獲得配置信息。如果我們變更Configuration的存儲形式,或者用其他的xml文件存儲,或者是存儲在數(shù)據(jù)庫,或者是從遠端的webservice里獲得配置信息,代碼就不得不做出修改,重新編譯。
2.?這class所擔負的職責要比他的名字所描述的要多。這個類提供發(fā)送郵件的功能,但是,不僅僅與此,這個類還會調用一個template 引擎對郵件的文本進行處理。由此帶來的另外一個問題就是,這個類必須具備對這個template 引擎的知識,知道如何調用這個引擎的方法。
這樣的2個主要問題帶來的影響就是,給這個類增加了兩個比較強的依賴關系。就是對外部config模塊和郵件template 引擎的依賴。也許有人會覺得,這沒有什么問題呀,我們平時寫代碼也是這樣的,沒有什么不好呀。可是仔細考慮一下,如果需求變動,config部分變更,配置的Key發(fā)生變化,或者配置從另外的xml獲取,或者配置從Database獲取,或者配置是從一個webService獲取,那么上面的這段代碼無疑就要進行修改。如果這樣的地方多了,那修改起來既容易錯,又容易發(fā)生漏改。再說template引擎,如果我們換了另外一個模板引擎,我們也需要類似的做修改。
從另外一個角度說,如果我們又有了一個類似的項目,里面也有發(fā)郵件的功能。通常,最快的方法就是把這些代碼copy過來,做少量的修改。但是,這個并不是一個好的,高效的做飯。同時,代碼的維護也變得多出了一倍。這里,又引入了一個我們在框架設計是的一個思想,就是組件化,服務化。而IOC容器就提供了一種手段和工具來幫助我們管理這些組件和服務。
?
什么是組件呢?我個人認為,組件就是一小段可重用的代碼單元。對外部,他應該提供一個職責明確的服務接口,也就是說,這個組件只處理他職責范圍內的事情,并且要處理得很好。通常來說,一個組件就是一個Class,實現(xiàn)了一個Service,也就是實現(xiàn)了一個interface。而這個interface就為我們引入了一個抽象層,把接口服務和實現(xiàn)分開。即我們常說的面向接口的開發(fā)。
再回到剛才的例子。
我們先定義Email的service
//?The?contractpublic?interface?IEmailSender
{
??void?Send(String?from,?String?to,?String?message)
}
這個接口描述了發(fā)送Email這個Service的契約,只要實現(xiàn)了這個契約所規(guī)定的,不管是用smtp,還是IMAP或者其他的什么的,我們都認為是合法的。
OK,接下來我們給契約增加一個實現(xiàn),使用smpt來訪發(fā)送郵件。
//?The?implementationpublic?class?SmtpEmailSender?:?IEmailSender
{
??private?String?_host;
??private?int?_port;
??public?SmtpEmailSender(String?host,?int?port)
??{
????_host?=?host;
????_port?=?port;
??}
??public?void?Send(String?from,?String?to,?String?message)
??{
????//?Configures?the?Smtp?class?and?sends?the?e-mail
??}
}
看起來,是不是感覺這樣做好了一些。現(xiàn)在這個類就只負責把郵件發(fā)送出去,并不負責對郵件的文本進行模板處理。
OK,我們再定義一個郵件Template的接口:
public?interface?ITemplateEngine{
??String?Process(String?templateName)
}光這樣一個ITemplateEngine還不夠,我們還需要一個組件來負責執(zhí)行模板轉換和分發(fā)郵件的功能。
public?interface?INewsletterService
{
??void?Dispatch(String?from,?String[]?targets,?String?messageTypeName)
}
OK,現(xiàn)在讓我們來考慮一下INewsletterService接口服務應該如何實現(xiàn)。很顯然,需要使用IEmailSender 服務 和 ITemplateEngine 服務,而不用關系IEmailSender和ITemplateEngine的具體實現(xiàn)是如何。
{
??private?IEmailSender?_sender;
??private?ITemplateEngine?_templateEngine;
??
??public?SimpleNewsletterService(IEmailSender?sender,?
??????????????????????ITemplateEngine?templateEngine)
??{
????_sender?=?sender;
????_templateEngine?=?templateEngine;
??}
??public?void?Dispatch(String?from,?String[]?targets,?String?messageTypeName)
??{
????String?message?=?_templateEngine.Process(messageTypeName);
????foreach(String?target?in?targets)
????{
??????_sender.Send(from,?target,?message);
????}
??}
}
現(xiàn)在看上去,是不是感覺好了很多。通過設計上的重構,良好定義對象的職責,這段代碼已經(jīng)變得比以前更加靈活和易于擴展了。但是,仍然有個問題存在。我們需要自己把所有有關的東西聯(lián)系起來,包括IEmailSender, ITemplateEngine, 然后把他們傳遞到INewsletterService。
這些我們是可以手工通過代碼來完成,但是,借助于IOC容器,我們可以使用另外一種方式來完成同樣的功能。
接下來,我以.net下的開源IOC框架Castle為例子來說明IOC容器的神奇之處。
下面這段代碼,就可以完成我們上面的功能
IWindsorContainer?container?=?new?WindsorContainer();container.AddComponent(?"newsletter",?typeof(INewsletterService),?
????????????????????????typeof(SimpleNewsletterService)?);
container.AddComponent(?"smtpemailsender",?typeof(IEmailSender),?
????????????????????????typeof(SmtpEmailSender)?);
container.AddComponent(?"templateengine",?typeof(ITemplateEngine),?
????????????????????????typeof(NVelocityTemplateEngine)?);
//?Ok,?start?the?show
INewsletterService?service?=?(INewsletterService)?container["newsletter"];
service.Dispatch("hammett?at?gmail?dot?com",?friendsList,?"merryxmas");
OK,讓我來解釋一下,上面的這段代碼里到底是如何發(fā)揮這神奇功效的。
1.???? 首先,在容器里注冊INewsletterService 服務,并指定服務的實現(xiàn)是SimpleNewsletterService,服務在容器內的索引Key是newsletter
2.???? 在容器里注冊IEmailSender服務,并指定IEmailSender的實現(xiàn)是SmtpEmailSender,服務在容器內的索引Key是smtpemailsender。容器會檢查SmtpEmailSender類,發(fā)現(xiàn)他只有一個帶參數(shù)的構造函數(shù)SmtpEmailSender(String host, int port),而host和port目前還無法獲得,這個我們在后面會對SmtpEmailSender修改一下,來修正這個問題。
3.???? 在容器里注冊ITemplateEngine服務,并指定ITemplateEngine的實現(xiàn)是NVelocityTemplateEngine,服務在容器內的索引Key是templateengine
4.???? 容器檢測SimpleNewsletterService,發(fā)現(xiàn)他的構造函數(shù)需要IEmailSender和ITemplateEngine,容器會先創(chuàng)建 IEmailSender和ITemplateEngine的實例,然后再創(chuàng)建出SimpleNewsletterService實例。
首先我們修改SmtpEmailSender的實現(xiàn),以適應容器。
public?class?SmtpEmailSender?:?IEmailSender{
??private?String?_host;
??private?int?_port;
??public?SmtpEmailSender()
??{
????_host?=?"mydefaulthost";
????_port?=?110;?//?default?port
??}
??public?String?Host
??{
????get?{?return?_host;?}
????set?{?_host?=?value;?}
??}
??
??public?int?Port
??{
????get?{?return?_port;?}
????set?{?_port?=?value;?}
??}
??//
}
如上所示,我們提供了一個無參數(shù)的構造函數(shù),并且提供了兩個屬性Host和Port。這樣容器可以創(chuàng)建一個SmtpEmailSender,并且通過讀取配置,來設置Host和Port。
一個簡單的配置文件的例子如下:
<?xml?version="1.0"?encoding="utf-8"??>??<configuration>?
????<configSections>
????????<section?name="castle"
??????????type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,?
????????????????Castle.Windsor"?/>
????</configSections>
?
????<castle>
????????<components>
????????????<component?id="smtpemailsender">
????????????????<parameters>
????????????????????<host>localhost</host>
????????????????????<port>110</port>
????????????????</parameters>
????????????</component>
????????</components>
????</castle>?
</configuration>
這樣,容器替我們完成了創(chuàng)建組件的任務。容器可以檢測容器內的組件的相互依賴性,并且可以通過外部的配置文件來配置容器的實現(xiàn),設置其屬性。
本質上來說,容器幫我們把組件之前的耦合性轉移到容器外的配置文件里。但是組件之間的依賴性是通過接口來弱化的,就只是一個松耦合的關系。
當然,容器的作用不僅僅與此,我們還可以為同一個服務指定多個實現(xiàn),通過配置,可以指定被依賴的服務采用何種實現(xiàn),可以為指定的組件定制自己ComponentActivator來控制其創(chuàng)建過程。還可以通過動態(tài)代理的方式,完成一些面向方面的特殊功能。
總之,IOC容器是非常強大的工具,是我們框架的基礎與核心,為框架的可擴充提供了必備的條件
轉載于:https://www.cnblogs.com/BlogNetSpace/archive/2012/03/20/1334174.html
總結
以上是生活随笔為你收集整理的.Net 应用框架设计系列(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何让插件加载到Qt Designer
- 下一篇: 转自 David dai linux平台