2009年4月28日 星期二

Why Interface? 上集: Homework - Observer Design Pattern

今天真是令人百感的交集一天,感嘆台灣軟工界師資水準的低落。
我想,走軟體這一行,還真的是業界才找得到比較深的人。
一位前輩在我準備回到學校前就告誡過我:「學軟工,還是在業界學得比較多,畢竟軟工重實務,非理論。」
其實他說的沒錯,要多做,才會參透,才會瞭解理論的梗倒底在哪裡。

今天的 Component Base Software Engineering 的課程,我上台報告我的 Homework ,如何用 Observer Design Pattern 來解決 Referential Integrity 的問題。

問題

什麼是 Referential Integrity 呢?舉例來說:我們有兩個在設計上相互獨立的 Component ,一個叫做 CustomerMgr ,一個叫做 HotelMgr ,CustomerMgr 裡面管了一個資料叫做 Customer , HotelMgr 裡面管了一個資料叫做 Reservation , Reservation 裡面有 Customer 的參照存在,當 Customer 資料被刪除時,希望相對應的 Reservation 資料也能做 delete cascade 或 update cascade 的動作。
如下圖:



前提是兩個 Component 在設計上要相對獨立,不能相依,請問該怎麼搞定?
元件架構如下圖:



在某本書上的大師提到,這個問題,可以用 Observer Design Pattern 來解,於是,這就成了我們的作業。

Observer Design Pattern 的詳細內容就不提了,基本上在實務上就像軟體上有個按鈕,不是會給他 Add 一個 Listener 嗎?然後按鈕被按了, Listener 就會聽說按鈕被按了,趕快去做一些事情,詳細內容請參照 Gof Design Pattern。

First Cut

我的第一個設計圖如下:


最簡單的解決方式就是 CustomerMgr 去 inherit Subject ,讓 CustomerMgr 擁有成為 Subject 的能力, HotelMgr 去多 realize 一個 Observer interface ,讓 HotelMgr 擁有 Observer 的能力,然後,把 HotelMgr attach 到 CustomerMgr 上,這樣 CustomerMgr 如果有變動,就可以通知到 HotelMgr 去做變更了。
他們實際上怎麼運作?請看以下循序圖:

當 CustomerMgr 元件的 deleteCustomer 被呼叫到的時候,除了刪除 customer 資料,還會去執行 notify method , notify method 裡面再去執行 Observer 的 update method 。


我們之前 attach 到 CustomerMgr 上的 Observer HotelMgr ,於是被呼叫 update method ,然後把自己的 Reservation 資料 delete cascade 掉。

以上應該就可以交差了,我想老師要的應該也只是這樣就夠了,但是我總覺得這樣的設計有損我的專業,應該再把他設計完美一點。

誰應該把 H 貼到 C 上?

我想到的第一個問題是,誰來把這兩個元件貼在一起?不可能是 CustomerMgr 把 HotelMgr 貼在自己身上,也不可能是 HotelMgr 把自己貼到 CustomerMgr ,因為這樣都會造成 CustomerMgr 與 HotelMgr 的相依。因此我覺得這件事情應該交給 Container 來完成,而且是 Deploy 的時候,依照 Deployment Descriptor 完成註冊,這樣會比較合理,所以我修改設計圖如下:

我簡單的描述了一個 DeploymentMechanism 的程式,兩條 dependency 的線代表 DeploymentMechanism 認得這兩個元件,而且會幫他們完成註冊,但在真正的實務上,這兩條線應該不會存在,因為 DeploymentMechanism 應該會透過讀 Deployment Descriptor 來認識這兩個元件,進而將這兩個元件貼在一起,不會真的直接在 Code 裡面認識這兩個元件,可透過 Java Reflection 機制達成。但我不畫那兩條線也怪,會讓人感覺沒有關連的東西,怎麼有辦法對兩個元件做動作,所以還是畫了那兩條線,避免又要解釋什麼叫做 Deployment Descriptor ,什麼叫做 Java Reflection ,然後兩個搭起來怎麼讓 DeploymentMechanism 運作。

把問題複雜化

接下來,還有一個問題是,如果今天 CustomerMgr 會發佈一個以上的 Subject , HotelMgr 會有一個以上的 Observer 該怎麼解?套用目前的架構,是無法解決的,因為目前的架構只支援一個 Component 可有一個 Subject 及一個 Observer 。
問題的根源在於目前的架構是 CustomerMgr 直接繼承 Subject 來用, HotelMgr 直接實作 Observer Interface 。
為了打破這個僵局,我們必須放棄使用繼承的方式來讓 CustomerMgr 有 Subject 的能力,因此我改而採用 Aggregation 的方式,讓 CustomerMgr 來使用 Subject ,這樣 CustomerMgr 就可以有多個 Subject 可以發佈。
HotelMgr 則改成,不直接實作 Observer 介面,而由額外的 ConcreteObserver 來實作 Observer Interface ,這些 ConcreteObserver 接到異動通知,再去通知 HotelMgr 做相對應的資料刪除或修改。
因此架構會修改如下:

CustomerMgr 元件會有一個 Subject ,這個 Subject 是 customerSubject ,未來如果 CustomerMgr 元件有另一個東西要發佈成 Subject ,只用再加一個 Subject 到 CustomerMgr 元件上就可以了,比如說, Company 資料也要被發佈成 Subject ,那就再多個 companySubject 給 CustomerMgr 。

此外, HotelMgr 這邊,可以實作多個 Concrete Observer ,比如說 HotelMgrObserverCustomer 用來監聽 Customer 資料的異動,若也要監聽 Company 資料的異動,就再實作一個 HotelMgrObserverCompany 的 Concrete Observer ,這些 Concrete Observer 收到通知後,則分別去對不同的資料進行刪除或更新的動作。

Separation of Concern & Open/Close Principle

最後,我覺得設計到現在,我們加了很多跟 pure 處理 business 不相干的設計進去,這樣做容易造成 business component 的污染,依據 separation of concern 原則,若可以將解決 business 相關的設計,與解決 referential integrity 問題的設計分開,應該會更好。

依據 open/close principle ,我們若希望能夠修改 business component ,讓他擁有解決 referential integrity 的能力,又不希望去修改 business component 本身,有一種做法就是 extend 他來改。
因此我將架構修改如下:

我們可以把處理 Referential Integrity 的程式往 Child Class 移動,讓 CustomerMgr 專心於 business 相關的程式即可,處理一些技術相關的問題,例如: transaction, persistence, referential integrity 等,技術相關的程式都往 child class 移動。
如果有玩過 EJB 的人,應該很容易就看出我的梗了,事實上 EJB 也是利用這種方法來實作,而且 EJB 會在 Deployment 的時候,才把技術相關的 Code 產生出來, Deploy 到 Container 上,因此,我認為, ChildCustomerMgr, ChildHotelMgr, HotelMgrObserverCustomer 這些 Code 也應該仿效 EJB ,在 Deploy 前再 Gen Code 出來,不應該由人來撰寫。人只用撰寫好 Deployment Descriptor ,然後 Container 讀了 Deployment Descriptor 後, Gen 出 ChildCustomerMgr, ChildHotelMgr, HotelMgrObserverCustomer 這些程式碼,而且把該貼在一起的貼在一起。
好,我的設計就到此告一個段落,再下去就太複雜了。

接著可以講今天的主題了,不過好像該分段了,有人說我的 Blog 都不分段, Readaility 很差。

沒有留言:

張貼留言