2004 年 3 月
在中小規(guī)模的軟件中,對(duì)象和對(duì)象之間的協(xié)作關(guān)系就能夠滿足需要。但是當(dāng)軟件規(guī)模擴(kuò)大,復(fù)雜度上升的時(shí)候,面向?qū)ο蠹夹g(shù)強(qiáng)調(diào)的協(xié)作卻表現(xiàn)出另一個(gè)極端的特點(diǎn)-耦合度太高導(dǎo)致的復(fù)雜度。這時(shí)候就需要有一種新的方法來(lái)彌補(bǔ)面向?qū)ο蠹夹g(shù)的弱點(diǎn)。
大規(guī)模軟件主要特點(diǎn)是復(fù)雜度。比較典型的例子是集成性的項(xiàng)目。軟件系統(tǒng)需要將各種各樣的硬件、遺留系統(tǒng)、外部接口整合起來(lái)。其間可能遇到不同的硬件接口,不同的操作系統(tǒng),不同的語(yǔ)言,不同的平臺(tái),不同的數(shù)據(jù)庫(kù),不同的消息中間件,不同的網(wǎng)絡(luò)介質(zhì)。這些都使得系統(tǒng)變得非常的復(fù)雜。
面向?qū)ο蠹夹g(shù)的特點(diǎn)是通過(guò)對(duì)象之間的職責(zé)分工和高度協(xié)作來(lái)完成任務(wù)。這樣的好處是代碼量較少,系統(tǒng)布局合理,重用程度高。但是當(dāng)對(duì)象的個(gè)數(shù)大量增加的時(shí)候,對(duì)象之間的高度耦合的關(guān)系將會(huì)使得系統(tǒng)變得復(fù)雜,難以理解。
以前對(duì)于這個(gè)問(wèn)題的方法是采用包(請(qǐng)參考拙作面向?qū)ο筌浖_(kāi)發(fā)中對(duì)包的相關(guān)討論)作為容器來(lái)組織對(duì)象,對(duì)象之間的依賴性將轉(zhuǎn)化為包之間的依賴性。這種方法聽(tīng)起來(lái)有道理,但是在實(shí)際中仍會(huì)出現(xiàn)難以解決的問(wèn)題。
包僅僅只是容器。這意味著對(duì)對(duì)象的組織可以是任意的,而包之間依賴關(guān)系的設(shè)計(jì)則還是取決于對(duì)象的依賴。此外,包的設(shè)計(jì)和對(duì)象一樣,缺乏一個(gè)統(tǒng)一的風(fēng)格。而統(tǒng)一的風(fēng)格正是大規(guī)模軟件設(shè)計(jì)所必須的,因?yàn)檫@樣可以有效改進(jìn)系統(tǒng)的可理解性,這一點(diǎn)非常重要。
面向組件編程的縮寫(xiě)是COP。COP是對(duì)OOP的補(bǔ)充,幫助實(shí)現(xiàn)更加優(yōu)秀的軟件結(jié)構(gòu)。組件的粒度可大可小,需要取決于具體的應(yīng)用。
在COP中有幾個(gè)重要的概念:服務(wù),服務(wù)(Service)是一組接口,供客戶端程序使用。例如,驗(yàn)證和授權(quán)服務(wù),任務(wù)調(diào)度服務(wù)。服務(wù)是系統(tǒng)中各個(gè)部件相互調(diào)用的接口;組件,組件(Component)實(shí)現(xiàn)了一組服務(wù),此外,組件必須符合容器訂立的規(guī)范,例如,初始化,配置、銷(xiāo)毀。
COP是對(duì)一種組織代碼的思路,尤其是服務(wù)和組件這兩個(gè)概念。在下文會(huì)提到的Spring框架中,就采用了COP的思路,將系統(tǒng)看作一個(gè)個(gè)的組件,通過(guò)定義組件之間的協(xié)作關(guān)系(通過(guò)服務(wù))來(lái)完成系統(tǒng)的構(gòu)建。這樣做的好處是能夠隔離變化,合理的劃分系統(tǒng)。而框架的意義就在于定義一個(gè)組織組件的方式。
在我的qca網(wǎng)站上提供了幾篇講解組件的文章,可以通過(guò)以下的鏈接訪問(wèn): http://qca.cn/common/content.htm
組件不是一個(gè)新的概念,Java中的javaBean規(guī)范和EJB規(guī)范都是典型的組件。組件的特點(diǎn)在于他定義了一種通用的處理方式。例如,JavaBean擁有內(nèi)視的特性,這樣就可以通過(guò)工具來(lái)實(shí)現(xiàn)JavaBean的可視化。而EJB規(guī)范定義了企業(yè)服務(wù)中的一些特性,使得EJB容器能夠?yàn)榉螮JB規(guī)范的代碼增添企業(yè)計(jì)算所需要的能力,例如事務(wù)、持久化、池等。
所以,組件比起對(duì)象來(lái)的進(jìn)步就在于通用的規(guī)范的引入。通用規(guī)范往往能夠?yàn)榻M件添加新的能力(就像上面所討論的),但也給組件添加了限制,例如你需要實(shí)現(xiàn)EJB的一些接口。以下我們將討論組件的一些相關(guān)問(wèn)題:
組件的粒度
組件的粒度是和系統(tǒng)的架構(gòu)息息相關(guān)的。組件的粒度確定了,系統(tǒng)的架構(gòu)也就確定了。在小規(guī)模的軟件中,可能組件的粒度很小,僅相當(dāng)于普通的對(duì)象,但是對(duì)于大規(guī)模的系統(tǒng)來(lái)說(shuō),一個(gè)組件可能包括幾十,甚至上百個(gè)對(duì)象。因此,對(duì)使用COP技術(shù)的系統(tǒng)來(lái)說(shuō),需要正確的定義組件的粒度。較好的定義粒度的方法是對(duì)核心流程進(jìn)行分析。
針對(duì)接口
接口和實(shí)現(xiàn)分離是COP的基礎(chǔ),沒(méi)有接口和實(shí)現(xiàn)的分離,就沒(méi)有COP。接口的高度抽象特性使得各個(gè)組件能夠被獨(dú)立的抽取出來(lái),而不影響到系統(tǒng)的其它部分。
接口和實(shí)現(xiàn)分離有以下幾個(gè)好處:
1、 在模塊/組件/對(duì)象之間解耦。
2、 輕松的抽換實(shí)現(xiàn),而不用修改客戶端。
3、 用戶只需要了解接口,而不需要了解實(shí)現(xiàn)細(xì)節(jié)。
4、 增加了重用的可能性。
IOC
IOC是Inversion of Control的簡(jiǎn)稱(chēng)。它的原理是基于OO設(shè)計(jì)原則的好萊塢原則(The Hollywood Principle):不要訪問(wèn)我,我們將訪問(wèn)你。也就是說(shuō),所有的組件都是被動(dòng)的(Passive),所有的組件初始化和調(diào)用都由容器負(fù)責(zé)。
在Brian Foote的論文(http://www.laputan.org/drc/drc.html)中解釋了IOC的概念。
qca網(wǎng)站上對(duì)IOC的討論(http://qca.cn/common/IOC.htm)
IOC有幾種實(shí)現(xiàn)的類(lèi)型,包括基于方法參數(shù)調(diào)用的Method-based (M) IoC,它把組件傳遞給每個(gè)方法調(diào)用;基于接口的Interface-based (I) IoC(通常稱(chēng)為類(lèi)型1),它使用接口來(lái)聲明組件之間的依賴性,例如,Serviceable, Configurable;基于Setter方法的Setter-based (S) IoC(通常稱(chēng)為類(lèi)型2),它使用setter方法來(lái)設(shè)置組件之間的依賴性;基于構(gòu)造函數(shù)的Constructor-based (C) IoC(通常稱(chēng)為類(lèi)型3)。
在Martin Fowler剛剛完成(2004-1-24)的Inversion of Control Containers and the Dependency Injection pattern一文(http://www.martinfowler.com/articles/injection.html)中,將IOC模式稱(chēng)為Dependency Injection模式。
IOC是框架開(kāi)發(fā)的一個(gè)基本原理。在開(kāi)源軟件中,不少的容器類(lèi)框架都采用了IOC的思路。例如PicoContainer(http://www.picocontainer.org/)和Spring(http://www.springframework.org/)
組件污染
在IOC的第一類(lèi)型中,由于組件需要實(shí)現(xiàn)一些特定的接口,或是從某個(gè)類(lèi)集成。這將使得組件受到一些約束(稱(chēng)為Invasive),例如組件移植不便。另一種情況是組件需要依賴于一個(gè)特定的容器,最為典型的就是EJB,組件無(wú)法脫離容器單獨(dú)存在,這也使得組件受到約束。這兩種情況都屬于組件污染。
最理想的組件是只專(zhuān)注于自身工作的組件,它沒(méi)有其它額外的邏輯。不過(guò)按照這種標(biāo)準(zhǔn),目前大部分的代碼都是不符合的。因此,目前在開(kāi)源軟件界出現(xiàn)了一些輕量級(jí)的容器框架,典型的如上文提到的PicoContainer和spring。他們的定位就是提供一個(gè)對(duì)組件管理的統(tǒng)一模式,而組件可以單獨(dú)的使用,也可以放在另一個(gè)容器中,容器僅僅只是為組件提供了一些額外的功能,和組件本身沒(méi)有直接的依賴。
為什么我們說(shuō)接口比繼承好,很重要的一個(gè)原因就是接口更加靈活,組件的依賴性更弱,同樣的,目前另一種做法則是采用一些標(biāo)記性的語(yǔ)言來(lái)實(shí)現(xiàn)比接口更大的靈活度。例如基于xml的配置文件,以及J2SE1.5中將要引入的屬性。
組件的測(cè)試
組件和容器的依賴脫離為組件測(cè)試提供了一個(gè)很好的環(huán)境。我們?cè)跍y(cè)試一節(jié)中討論過(guò)容器內(nèi)測(cè)試往往是比較麻煩的,其原因就是在于上面所說(shuō)的組件污染問(wèn)題。例如在spring框架中,組件是一個(gè)標(biāo)準(zhǔn)的JavaBean,你可以直接編寫(xiě)代碼來(lái)設(shè)置組件的屬性和定義組件之間的依賴關(guān)系(適用于自動(dòng)化測(cè)試),也可以把這項(xiàng)工作交給spring容器(適用于開(kāi)發(fā)和部署)。
組件測(cè)試在測(cè)試的分類(lèi)中屬于集成測(cè)試。
在討論組件時(shí)我們談?wù)摿私M件粒度的問(wèn)題。當(dāng)組件的粒度不僅僅限于單個(gè)對(duì)象的時(shí)候。在構(gòu)成組件的多個(gè)對(duì)象中,有些對(duì)象處于組件內(nèi)部,不和其它的組件交互,有些對(duì)象需要和外部組件進(jìn)行交互。后一種對(duì)象起的就是服務(wù)的作用。在設(shè)計(jì)模式中,這種設(shè)計(jì)被稱(chēng)為Fa?ade模式。而在OO語(yǔ)言中,他們相當(dāng)于接口的概念。不管如何比喻,服務(wù)訂立了組件和組件之間的契約。這種契約是穩(wěn)定的(如果業(yè)務(wù)需求是穩(wěn)定的),不會(huì)隨著組件內(nèi)部的變化而發(fā)生變化。
要理解這一點(diǎn)也非常的容易。對(duì)于一個(gè)提供用戶認(rèn)證的組件,一個(gè)可能的服務(wù)對(duì)用戶進(jìn)行認(rèn)證和授權(quán),至于組件內(nèi)部采用LDAP還是關(guān)系數(shù)據(jù)庫(kù)來(lái)存放用戶信息,對(duì)服務(wù)來(lái)說(shuō)沒(méi)有任何的差別。
這樣做的好處有很多,一是組件之間能夠以一種穩(wěn)定的方式存在,組件內(nèi)部的變化不至于擴(kuò)散到整個(gè)軟件系統(tǒng)。二是軟件設(shè)計(jì)將會(huì)轉(zhuǎn)向重點(diǎn)設(shè)計(jì)組件之間的服務(wù),而組件的實(shí)現(xiàn)細(xì)節(jié)將會(huì)隱藏起來(lái),這不但有助于設(shè)計(jì)者更好的把握軟件的全局架構(gòu),而且有助于分工的細(xì)化。
服務(wù)并不是什么新穎的概念,RPC、IDL都是類(lèi)似的技術(shù)。但我們談的服務(wù)側(cè)重架構(gòu)和理念,不涉及到具體的技術(shù),這一點(diǎn)同SOA和WebService的關(guān)系類(lèi)似-SOA是一個(gè)結(jié)構(gòu)性的概念,而WebService是實(shí)現(xiàn)SOA的一種適合的技術(shù)。 Stairway to Heaven - Service Oriented Architectures(http://www.enterpriseintegrationpatterns.com/docs/stairwaytoheaven.pdf)一文討論了SOA需要注意的問(wèn)題,并提出了實(shí)現(xiàn)SOA的三個(gè)階段,本文所討論的服務(wù)為文中所討論的階段3,也是最理想的階段。
服務(wù)最好實(shí)現(xiàn)為接口。原則上服務(wù)可以是任何一種技術(shù):JMS、WebService、RPC、或是簡(jiǎn)單方法調(diào)用。但是出于服務(wù)的穩(wěn)定性的考慮,我們不應(yīng)該將一個(gè)服務(wù)和具體的技術(shù)綁定起來(lái),這樣會(huì)使的服務(wù)發(fā)生變化的可能性增大。在Java語(yǔ)言中,接口是具有極大的靈活性的,因此,將接口實(shí)現(xiàn)為普通的Java接口是較好的選擇。不過(guò),這樣做的話,我們也許就不能夠使用遠(yuǎn)程調(diào)用,Web服務(wù)之類(lèi)的功能了,不過(guò)這并不要緊,以下就是原因。
服務(wù)適配器
客戶端可以直接使用接口,也可以通過(guò)適當(dāng)?shù)倪m配器來(lái)將普通的接口服務(wù)轉(zhuǎn)換為特定技術(shù)實(shí)現(xiàn)的服務(wù)。

如上圖所示,一個(gè)普通的接口通過(guò)適配器模式轉(zhuǎn)換成和特定技術(shù)相關(guān)的服務(wù)。在JMX技術(shù)中,也采用這種方式,JMX平臺(tái)能夠?qū)⒁粋€(gè)普通的服務(wù)端口通過(guò)適配器進(jìn)行轉(zhuǎn)換,以適用于各種的協(xié)議,例如http、sock、snmp等等。
AOP技術(shù)對(duì)服務(wù)的幫助
由于有很多的文章討論了AOP技術(shù),我們這里就不再重復(fù)了,所以以下的內(nèi)容是假設(shè)你已經(jīng)擁有基本的AOP概念。在qca網(wǎng)站上對(duì)AOP做了一些簡(jiǎn)單的整理。(http://qca.cn/common/content.htm) AOP作為OO技術(shù)的補(bǔ)足,能夠以一種優(yōu)雅的方式來(lái)處理系統(tǒng)的橫切點(diǎn)。服務(wù)層面是應(yīng)用AOP的絕佳位置:

在上圖中,我們看到一個(gè)普通的用戶注冊(cè)服務(wù)通過(guò)AOP可以動(dòng)態(tài)的添加各種各樣的能力。AOP提供了幾個(gè)好處,一是能夠通過(guò)簡(jiǎn)單的代碼為所有的服務(wù)添加功能,而不用為每個(gè)服務(wù)編寫(xiě)代碼,從而大大節(jié)省了代碼量;二是把橫切點(diǎn)分離出來(lái),這樣服務(wù)僅保留了核心的代碼,提高了系統(tǒng)的模塊化程度;最后一點(diǎn)是模塊化的增加使得為服務(wù)動(dòng)態(tài)的增加或刪除功能成為可能,例如,可以通過(guò)配置動(dòng)態(tài)的將新的Aspect添加到用戶注冊(cè)服務(wù)上。
服務(wù)的測(cè)試
服務(wù)測(cè)試在測(cè)試的分類(lèi)中屬于接受測(cè)試。服務(wù)概念的引入使得自動(dòng)化的接受測(cè)試變得容易了。在大規(guī)模的軟件設(shè)計(jì)中,業(yè)務(wù)流程往往涉及到各種組件通過(guò)服務(wù)介面的相互協(xié)作。所以這就是測(cè)試的重點(diǎn)。回到我們之前討論的組件粒度問(wèn)題,如果此時(shí)你編寫(xiě)出的測(cè)試代碼過(guò)于繁瑣,說(shuō)明組件的設(shè)計(jì)粒度太小了,如果組件的粒度太大,你會(huì)發(fā)現(xiàn)有些測(cè)試代碼根本無(wú)法編寫(xiě)。
服務(wù)的管理
服務(wù)的管理是一個(gè)比較大的話題。一方面,在一個(gè)大規(guī)模的系統(tǒng)中,雖然通過(guò)組件和服務(wù)的形式能夠降低系統(tǒng)的復(fù)雜度,但是服務(wù)仍然很多,需要進(jìn)行管理;另一方面,服務(wù)的狀態(tài),服務(wù)的可用性需要監(jiān)控和管理,這對(duì)于大規(guī)模應(yīng)用來(lái)說(shuō)是必須的。因此,服務(wù)需要一種管理形式。
JMX規(guī)范提出的目的也是一個(gè)對(duì)各種不同的組件進(jìn)行統(tǒng)一管理。和我們所闡述的有類(lèi)似之處。JMX分為規(guī)范和遠(yuǎn)程接口兩個(gè)部分,在J2SE1.5版本中,JMX已經(jīng)納入到J2SE的范疇中了,有興趣的讀者可以參考sun的相關(guān)文檔。
直到目前為止,軟件開(kāi)發(fā)仍然屬于手工作坊階段,如果要和制造業(yè)的水平相對(duì)應(yīng)的話,基本上是處于蒸氣機(jī)發(fā)明之前的水平。隨著軟件開(kāi)發(fā)技術(shù)的發(fā)展,軟件開(kāi)發(fā)也將象制造業(yè)一樣,步入"工業(yè)"時(shí)代。不過(guò)對(duì)于軟件開(kāi)發(fā)來(lái)說(shuō),機(jī)器仍然是代碼,只不過(guò)這些代碼是用來(lái)代替開(kāi)發(fā)人員的編碼活動(dòng)的,他具有比手工編碼更高的生產(chǎn)力。我們把這些高產(chǎn)量的"機(jī)器"描述成現(xiàn)代化的軟件工廠。那么,軟件工廠到底是什么?要了解軟件工廠,我們需要先了解軟件總線的概念。
COP僅僅只是提出了一個(gè)系統(tǒng)劃分的基礎(chǔ),要構(gòu)成一個(gè)完整應(yīng)用,光有組件和服務(wù)還不夠,還需要將組件和服務(wù)以一種有效的方式組織起來(lái),有些文章把這種組織性的代碼稱(chēng)為 Fabric,有結(jié)構(gòu)和組織的意思。而在我們的文章中,稱(chēng)之為軟件總線(bus)。
軟件總線是什么?他和計(jì)算機(jī)的總線一樣,它負(fù)責(zé)在各個(gè)組件中傳遞信息流,將各個(gè)組件組織起來(lái),完成一個(gè)具體的任務(wù)。總線是一個(gè)抽象的概念,在實(shí)際中總線也是由具體的技術(shù)構(gòu)成。例如,一個(gè)總線可能是一段代碼,負(fù)責(zé)調(diào)用各個(gè)組件;總線也能是一個(gè)消息系統(tǒng),負(fù)責(zé)收集和分派消息;總線也可能是一個(gè)工作流系統(tǒng),負(fù)責(zé)系統(tǒng)信息的流轉(zhuǎn);總線還可能是一個(gè)JMX,負(fù)責(zé)將消息路由到目標(biāo)組件。但無(wú)論總線的實(shí)現(xiàn)技術(shù)是什么,總線的特點(diǎn)就是采用一種松耦合的方式將組件組織起來(lái)。這樣,總線本身和掛接在總線上的組件就是松耦合的,至于組件掛接到總線的形式,也就是我們之前討論過(guò)的服務(wù)和服務(wù)適配器要做的事情了。
例如,目前的軟件總線有三種可能的實(shí)現(xiàn):直接調(diào)用/遠(yuǎn)程調(diào)用/WebService,MOM,以及工作流,根據(jù)應(yīng)用系統(tǒng)的特點(diǎn)可以采用不同的總線實(shí)現(xiàn)。例如以調(diào)用為主的總線適用于那些流程比較明確的應(yīng)用,因?yàn)榱鞒淌怯簿幋a的,變化起來(lái)相對(duì)麻煩一些。工作流為主的總線適用于流程比較靈活,需要復(fù)雜的分支和人為的干預(yù)。MOM為主的總線則適用于大型的分布式,或是異構(gòu)的應(yīng)用,不同的應(yīng)用之間以一種松散的方式進(jìn)行協(xié)作。但是無(wú)論采取哪一種的總線實(shí)現(xiàn)方式,組件和服務(wù)是不變的,變化的是服務(wù)適配器,MOM的服務(wù)適配器和工組流的服務(wù)適配器是不同的。MOM的服務(wù)適配器主要的工作是將消息中的內(nèi)容翻譯為POJO,并調(diào)用服務(wù);而工作流的服務(wù)適配器可能就只是一個(gè)基于當(dāng)前工作流狀態(tài)的調(diào)用。這樣形成的系統(tǒng)架構(gòu)就是相對(duì)穩(wěn)定、松散耦合的,不論是組件發(fā)生變化,還是總線的技術(shù)發(fā)生變化,只要服務(wù)和總線的規(guī)范是穩(wěn)定的,整體的軟件系統(tǒng)就是穩(wěn)定的。而服務(wù)和總線規(guī)范才是軟件組織的核心競(jìng)爭(zhēng)力所在,而這也正是軟件總線的主要目的。
Patterns and Best Practices for Enterprise Integration (http://www.enterpriseintegrationpatterns.com/)中收集整理了大量有價(jià)值的和消息相關(guān)的企業(yè)集成模式
軟件工廠在組件和軟件總線的基礎(chǔ)上發(fā)展,并根據(jù)組件和軟件總線的技術(shù)特點(diǎn),定義了一系列的管理活動(dòng),以提高開(kāi)發(fā)效率。軟件工廠是我們定義的軟件質(zhì)量框架的一種實(shí)現(xiàn)方式。對(duì)于不同的軟件組織來(lái)說(shuō),根據(jù)自身的研發(fā)特點(diǎn)來(lái)定義軟件工廠的構(gòu)成是非常重要的。具體的內(nèi)容包括管理實(shí)踐的選擇,組件的積累,軟件總線的技術(shù)實(shí)現(xiàn)。在本系列文章中推薦的前兩項(xiàng)實(shí)踐都屬于管理實(shí)踐范疇的內(nèi)容,而第三項(xiàng)實(shí)踐則偏重于建立軟件工廠的底層支撐框架。
軟件工廠的概念代表了一種新的軟件開(kāi)發(fā)模式。他的優(yōu)勢(shì)在于能夠把技術(shù)和管理結(jié)合起來(lái),提高生產(chǎn)力。
關(guān)于作者 林星,致力于研究敏捷理論和優(yōu)秀的軟件設(shè)計(jì)思想,并將之應(yīng)用于國(guó)內(nèi)的軟件組織。可以通過(guò) iamlinx@21cn.com 和他聯(lián)系,也可以通過(guò)訪問(wèn) www.qca.cn 和 www.aglichina.org 來(lái)獲得更多的信息。 |
本博客為學(xué)習(xí)交流用,凡未注明引用的均為本人作品,轉(zhuǎn)載請(qǐng)注明出處,如有版權(quán)問(wèn)題請(qǐng)及時(shí)通知。由于博客時(shí)間倉(cāng)促,錯(cuò)誤之處敬請(qǐng)諒解,有任何意見(jiàn)可給我留言,愿共同學(xué)習(xí)進(jìn)步。