« 物件導向不再無限上綱 | 網頁 | 讀書會 -- 該不該辦? »

16/02/2006

物件導向不再無限上綱

這是我在之前的 blog 中所提到的觀點。有幸 Jini 也在他的 blog 中提出回應,讓我有機會,將這樣的想法作更進一步釐清。

首先讓我聲明一點,在我的前文中,以「EJB 3.0 叫好不叫座」作為標題的一部分是不適當的,因為該段的內容所評論的,主要是針對物件導向方法與軟體架構設計而發。把 EJB 3.0 放在一起討論,將不相關的議題混在一起,模糊了敘述的主軸。

所以,在這篇 blog 中,我將針對物件導向方法與軟體系統設計上的一些心得,加以陳述。

* 典範轉移,物件導向不再獨領風騷

近兩三年來的確有許多軟體設計上的新思維,取得了前所未有的注目,諸如 Aspect-Oriented Programming(AOP)、Service-Oriented Architecture(SOA)、Meta Programming(以 Java 來講是 Attribute/Annotation-Oriented Programming、Declarative Programming,甚至簡單講就是 reflection)。當然這些程式設計典範(paradigm) 在推出時,都會強調並不是用來取代物件導向設計,而是與物件導向相輔相成。

讓我這樣說吧! 以上所出現的這些新的設計典範,沒有一個是在設計小程式時需要用著的。你會發現不管是 AOP, SOA, MP 甚至是簡單的 reflection,都是在設計大系統、設計 framework 才會用著的東西。例如,EJB 3 中大量採用 Annotation 來簡化部署描述,為什麼?以物件導向的觀念來說,一個系統最好全部採用物件導向的機制來實作,這樣才有「概念的整體性」性嘛。Annotation 算是物件導向技術嗎?我不認為。那為什麼還要犯了「概念整體性」、「思想純淨性」的忌諱採用這種方式呢?

原因是很簡單的,當我們(至少是我)開發大系統或 framework 時,我需要元件(想像成 EJB) 與平台(想像成 JavaEE 容器) 間,或各個元件彼此之間有更容易的溝通與互動方式,也就是我前文所講的:

當你往上堆砌系統,或一開始即採用架構導向系統開發方法(Architecture Oriented System Development)時,比較適當的思維模式,是將系統看作一部"機器",你考慮的應當是系統各部元件之間的連結,彼此間如何互動,最後才是各部元件 各自的角色與職責。

以純淨的物件導向技術來實作的話,容器或框架會定義出一些(有時是很多)介面,讓符合其規格的元件實作。當然這就算是一種 IoC(Inversion of Control),一種 callback。而這也是 EJB 2 大量採用的方式。但為什麼 EJB 3 要採用 Annotation 呢?這證明什麼,至少在某方面,Annotation 的思維可能較之物件導向思維,更適於解決某些系統間的溝通、互動角色。

* SOA 與物件導向思維之關係

SOA 的確如 Jini 所說,是透過支援一致的呼叫介面,讓異質系統可以互相串連的(當然用在同質系統中也行啦!)。然而我認為服務導向(或訊息導向) 與物件導向精神還有一個很大的不同點:服務導向架構更傾向使用 coarse-grain 的 API。相對的,物件導向方法為了充分表達領域物件的語意,通常會設計出 fine-grain 的 API。coarse-grain API 跟 fine-grain API 有什麼差別呢?

我以 EJB 中最符合 SOA coarse-grain API 型式的 JMS 來說明。我們撰寫 MessageDrivenBean 時,通常只需撰寫 MessageListener 的 onMessage(Message inMessage) 方法,然後在這個方法中,呼叫其他的物件(有可能是以 fine-grain style 寫成),就可以製造出一個能夠自由組裝資訊流程的系統。試想,如果我們這裡不用 coarse-grain API,而用 fine-grain API 來作為訊息傳遞介面會如何?我的經驗是,fine-grain API 大大的阻絕了物件/類別的重用性。這一點只要想想,用 SessionBean 是否可以像 MessageDrivenBean 那樣擁有組裝的靈活性就可明白。所以這裡的重點是,fine-grain API 適合用來表達領域物件,而 coarse-grain API 適合用來作為整個系統的通訊介面。

* Annotation、Reflection 與物件導向觀念可能潛藏的衝突

這裡我說的是 "潛藏",也就是在不良設計的情況下,並不是每個用到 Annotation、Reflection 技術的程式都會與物件導向觀念產生衝突。我從物件導向的三個特性說起。物件導向的特性是「封裝」、「繼承」與「多型」。接著我們再來想想 Annotation 的運作方式,它是在原始程式碼中定義一些 -- 好吧,就是 annotations,然後呢,在 compile time 或 rum time 時針對這些 annotations 做些處理。

當我們自己寫程式去處理 annotation 時,事實上是敲開一個類別,東看看、西看看 -- 偷窺別人的內在總是自在又痛快的事情,如果能夠痛下 鹹豬手自由 "存取" 一番,那更是淋漓盡致啦 -- annotation + reflection 給你這項偷偷摸摸的特權。而 (該死的) AOP, byte code generator 更讓你能打開類別的外衣,在裡面亂搞一通。

最明顯的,這裡嚴重的侵犯了類別的隱私(封裝性)。透過 reflection,你可以把類別的祖宗八代都叫出來。然後呢,有些本來該是用多型手法解決的問題,卻透過 annotation 或 AOP 之類的技法去解決。就算你的 annotation 或 AOP 設計精良,很可能程式的可追蹤性已變差;若是設計不良,就會產生比用 switch 敘述來取代多型的 bad smell 更難聞的 terrible smell. 所以這裡重點是,Annotation 不但與物件導向無關,甚且有害於物件導向。為採用 Annotation 的系統框架所寫的元件,極可能比採用 Dependence Injection 進入系統中的 POJO 元件更不易在別處重用。

* 同樣是 AOP,也有符不符合物件導向精神的區別

AOP 有好幾種實作方式,一種是直接在 java 語言中,增加支援 AOP 的語法,然後在編譯期將 AOP 的語法 waving 到 class byte code 中,這是 AspectJ 採用的方式。另一種是在架構上,以物件導向的方式(例如 proxy/interceptor pattern),實作對 AOP 的支援,這是 Spring AOP 所用的方式。通常前一種方式對 AOP 特性的支援較為完整,例如可以在 statement 中對被剖析的程式動手腳(加入 advice);後者則較差,像 Spring AOP 最細的 pointcut 就只到 method 層級。

照理說既然第一種方式對 AOP 的支援較為徹底,那我們應該多多採用 AspectJ 囉?事實上,我個人卻較傾向 Spring AOP。原因是,在 Spring AOP 中,可以用物件導向方式解決的,都盡量採用物件導向方式解決了。在 Spring AOP 中,不管是 pointcut 或是 advice 都是在實作特定的介面,因此這些類別,就算離開 Spring AOP 的環境,也還有重用的機會(只要你高興,你可以在一般的物件中呼叫)。而 AspectJ 的作法是增加 java 的語法,加入 aspect 元素(就像 class 元素一樣),在其中定義 pointcut。然而 aspect 與 class 的關係是單向的,不用說這些 aspect 不能離開 AspectJ 的編譯器下運作,就算是一般的類別,都不能呼叫這些 advice(對,懂得人會問為什麼要這麼做?我的理由是,給我自由。)

這裡的重點是,對於解決一個問題,你可能有好幾種作法。這幾種作法中,有些作法可與現有系統完美的搭配,而有些作法卻是充滿阻抗,與系統格格不入。選擇很明顯,給我 AOP,但請盡量用物件導向的方式。

* 有時候就是用不到物件導向的思維

或說的仔細一點,不要物件導向過了頭。例如,你在開發報表程式,你希望這樣的程式可以在資料庫欄位異動時,仍可在畫面上正常顯示。這時候,你絕不會想要對 報表所在的表格進行領域塑模。因為這時候你該注意的,不是表格代表的領域觀點,反而是所有資料庫中共有的架構特性,你該塑模的,是想辦法將資料庫綱要,與報表的樣版合協互動的方法。沒錯,報表程式中的資料庫綱要在 Java 中的確也是用 class 來表示,你會比較看重它的連結(介面)觀點。

其他的程式,像是 OLAP 程式,或是可以自由組裝欄位內容、資訊分類的 CMS 系統,我也是寧願採用較為簡單的 ER 觀點。直接處理表格裡面的欄位資料,會比較具有靈活性。這有點類似於 coarse-grain API 的作法。

* Java 失去物件導向的純淨性,卻換來開發應用系統更大的自由

Java 1.0 與現今的 Tiger 相較,哪個 Java 版本較接近純淨的物件導向語言呢?無疑的大家會同意是 Java 1.0。任何事物一開始出現,總是有美好的中心思想,但受到世俗濁流的影響,不可避免的要由純淨走向混亂。有些語言為了避免被人發現這個現象,乾脆直接招認。像是 C++ 說自己是 Multiparadigm Programming,Perl 的哲學是 There’s More Than One Way to Do It。那麼 Java 呢?

當物件導向取代結構化程序設計觀點時,我們也是花了好久習慣,但終究沒有引起太大的阻力。原因是物件導向的功能完完全全的含括了結構化設計的所有功能。今天的困難是,還沒有一種獨立、總括的思維,可以完全含括物件導向的思維。或許架構 + 元件算是吧! 但是為了解決架構與元件間連結、互動的問題,我們採用了種種沒有中心思想的作法。失去了純淨,增加了選擇。

結論

群雄並起,各領風騷。物件導向架構的語法元素,取代程序性語法元素,成為基本的建構單元。今天我們看待一個 class,就像往昔我們看待一個 procedure, function 那樣自然。

一開始的時候,程式是資料結構 + 演算法。後來呢,程式變成是一堆一堆物件的連結,但其實物件裡面也是資料結構 + 演算法。而今呢,注意一下你的企業系統:Data Warehouse + SOA。Data Warehouse 是比較大的資料結構,SOA 是比較大的演算法(流程 -- 活動的組合)。

看起來好像沒什麼不同,不過,以前你企業裡面的資訊系統是一座座孤島。而今呢?整個企業是一支可以合協運作的大程式。這就像是一堆單細胞生物,與一個人的區別。

留言

不得不對 edward 的心思縝密與技術能力感到佩服:)

不過我漸漸認為, 大多 Java EE 5.0 的 @Annotations 比較算是偏向 AOP 的延伸, @Resource 才算是簡化部署描述.
例如, Transaction 與 Security 的機制, 也包含了 @Interceptors 與 ArroundInvoke 的機制.

未來, 我相信許多 AOP 的 Opensource 或套件, 應該都會盡量採用 metadata 的方式來嵌入到系統開發之中.
例如.. AspectWerkz, JBossAOP, AspectJ 等等..
的確.. 如果大量採用 Annotation 若沒有特別的實作規範, 將來的 Debugging 可能會更為複雜 :(

Anyway.. 感謝 edward 為我上了一課 :)
改天記得再出來吃飯

ps. 生日送我的那兩張彩券.. 都有中 200 元

發表人: jini(99% jakarta) | 18/02/2006

出來吃飯可以,只是這一次該換我請客了 :)
至於上了一課嘛,只怕自己不要野人獻曝才是。
況且,我也是從你那兒學到不少東西啊...

發表人: Edward | 21/02/2006

不知道能做些什麼 只好幫忙校訂錯字了 希望對哪天要出版成冊時有點小小助益^^

Java 1.0 與現今的 Tiger 相較,……。有些語言為了避免被人發現這個現象,乾脆直接認 招。→乾脆直接"招認"??

當物件導向取代結構化程序設計觀點時,……,我們採用了種種沒有中心失想的作法。→沒有中心"思"想??

發表人: R-way | 05/03/2006

R-way ,謝謝你這麼認真的閱讀,有點感動呢。
中心 "失" 想我已加以更正。

而 "認招" 呢~ 我的意思是認了、招了的意思,所以就簡寫成認招。
不過在教育部國語辭典(http://140.111.34.46/dict/?open) 上的確找不到認招,只有招認。
可能我是受了連續劇的影響

發表人: Edward | 08/03/2006

發表留言