2007年7月31日 星期二

C19 Controller的設計(1)──Business Logic


這張圖是我所認定整個系統具有的完整層次。Controller的部分是以淡藍色所顯示的部分。

在Controller裡所要設計的是輸入結束後的系統處理,通稱為Business Logic的部分。第一個進入的Controller是對應Use Case的Activity Diagram的控制物件,本身只負責流程該換到哪個Activity執行而不要有任何其他的實作摻雜在裡頭。

第二層的Activity Controller裡是Activity應有的邏輯控制,在設計的時候同樣先用文字簡單表達處理的步驟,每一個小步驟都對應到一個Controller Service裡的方法。同樣地這裡純粹是控制,不要有其他的處理。有些系統的開發會把這兩層的控制包裝成Operation Flow與Operation Step的交易流程套件來減少額外的開發。

實作的功能都放在Controller Service這一層裡。比對View Service來看,我們可以發現Service才是系統真正放置功能的地方,唯有在這裡才允許存取或操作到不同層級的物件。在Service裡都是處理動作的實作,每個動作的設計都必須決定是要使用現有的Component、自己設計新的適用Component或是自己寫程式在方法裡處理。(這裡的Component必須與Business Logic無關)在確認Service動作的同時也必須要設計或定義使用Component的介面。

驅動程式各部件運作的關鍵層次,就是Service;經由這裡存取Model與View,並達成與外部的連接,一切功能需求裡提到的系統互動動作,大多會在這個層次實現。

2007年7月30日 星期一

C18 View的設計(3)──進入Controller

這層設計的一開始,要先決定View進入Controller的對應事件與傳遞的物件。在一開始的基礎關係,每個功能會有自己的View,自己的Controller,如果不去抽取相同的部分就會得到前面提到的大學生程式類型,造成每個功能都有一大堆重覆的程式碼。我們可以在View與Controller之間使用Façade Design Pattern作為唯一入口,在執行事件的Service裡把View的欄位與值在listener包裝成適當Model傳入Controller。

以Façade類別作為分界點,無論使用哪一種GUI元件開發顯示畫面,只要在進入執行時傳入指定的Model,後面的Controller都可以正常地處理;反過來看,只要Controller都符合傳入Model的介面,把畫面的Listener組成該Controller所需的Model就可以呼叫它。這便是用來應付可能發生的變化的設計應對。

許多經驗較少的人把交易的邏輯寫在顯示的類別裡,在處理資料的時候直接對顯示物件操作。由於指定物件本身的類別名稱,就會造成處理與顯示之間的密合而無法隨意更換,這樣的設計就像是一體成型的機器人手臂;因此我們需要在意義不同的層次上加工製作隔開兩邊的介面物件,藉此來達到可以抽換另一端實際執行物件的活動機制。

2007年7月29日 星期日

C17 做人的方法(4)──人會是Listener

在忙碌的工作裡,我們需要安排自己的工作項目與時間分配。手邊有哪些工作,每個工作的開始日與完成日都要牢記;還要記得所有會議的時間與地點,會議中進行哪些討論,要先準備哪些物品。在工作安排初期與會議敲定的瞬間,心裡都知道有那些事的存在,但是時間點到的時候能保證全部都記得嗎?

任何事都可能會忘記,一旦忘記沒做好接下來可能有重大的影響,許多人會用筆記本記錄未來要處理的所有項目,每天不斷地檢查清單的內容看有哪些項目是現在要做的。這像是使用polling的呼叫模式,每隔一段時間就必須記得去檢查,有狀態的改變時就去處理;但也會有一些時候檢查了也沒有要做的事浪費檢查的時間,或是在該去檢查的時間點沒去檢查而錯過時機。

我喜歡用PDA記錄工作項目與行事曆。該在什麼時間準備什麼樣的資料與做什麼事,全都記錄在行事曆中並設定鬧鐘提醒,時間點一到PDA就會自動提醒該做事項的內容。平常要記的事情已經夠多了,再加上這類瑣碎的事情實在會煩死人;使用行事曆後就能放空這些小事來專心工作,等到PDA提醒該做事的時候,再參照工作項目的內容去處理要做的事實在是輕鬆多了。

面對雜事的時候可以只是單純的Listener等候通知,但是處理工作事務的時候就不能被動成這個樣子。老板們總希望每位員工都是運作良好的Controller,能夠把手邊所有的工作項目理想地安排並完全地掌握所有事項的進度與狀況;這也是每個人都應該做到的基本能力。

2007年7月28日 星期六

C16 View的設計(2)──事件與處理

畫面在顯示之後就等待使用者的輸入與操作。所有程式語言工具對於畫面操作事件都有詳細的規畫與定義,所以大家對Listener介面的使用都很熟悉。這裡想要討論的是Listener的實作與事件對應處理邏輯的關係。

由於操作事件會跟隨著畫面作反應,所以把Listener的實作放在畫面呈現的類別裡是直覺的設計想法。我們發現有時同樣的事件與處理大量地重覆出現在很多畫面(例如系統規定Enter鍵是執行功能的熱鍵),在這樣的情形下相信所有的人會把Listener拉出到一個專門的Class來處理同樣的事件。

現在在這裡最常遇到的問題,是在拉出Listener後仍然把事件發生的對應處理寫在Listener裡面,造成難以切割的密合狀態。這時只要事件與處理邏輯之間的對應關係有所改變,都必須搬動整段程式碼才能達成;就算把這些處理提出到獨立的方法已經有做抽取動作,但也不算是正解。因為,無論如何都會動到程式碼。

如果認真“回憶”的話,我們在需求階段製作的Activity Diagram有分User與System的Swimlane,從User指向System的線條就是event,進去後系統的連鎖Activity正是系統要對應處理邏輯。我們要注意的是,反應由一堆Activity依序組成,每個Activity對應到一個ViewActivity物件的話,管理執行順序的工作交由一個ViewFlow物件處理,Activity的處理內容再交由View Service處理。

右上角的藍色框就是對應每一個event所應該設計的反應機制,當需要再往後執行功能邏輯時,會再往後面的層次傳遞。


2007年7月27日 星期五

C15 做人的方法(3)──把用過的資源放到該放的地方

宣告記憶體佔用,並在使用後釋放乾淨,這種維持系統運作正常的原則,同樣也是人類在日常生活中對於環境應該遵守的準則。

要記得我們多做了不該做的或少做了應該做的事時,周遭的人事物必定會受到相對的影響。例如拿出來用的物品在使用後要放回原處,這樣所有人才能照SOP規定的步驟在該拿到物品的地方取得,今天我們拿了物品不放回去,必然會造成應該取得物品的動作拿不到,同時不該有那項物品的地方多了這件物品。這便是相對的影響。

現在常看到很多人在路上隨手丟煙蒂、紙屑,丟棄後還能夠輕鬆自在地離去,從環境的角度上來看那個地方就多了一項破壞景觀的物品;隨處丟棄不想養的寵物也是這樣,把自己應該處理的事推託給別人或是大環境裡,個人所減少的負擔就這麼加重在其他人的身上。把物品在正確的時間點放到它該存在的地方,同樣是我教育小孩的信條。

每個元件如同個人一般把自己經手過的物件歸回到處理前的狀況,那個系統就會如同大環境一樣得以長久地良好存在。裡頭有任何一個元件沒有做好,就會有相對的小小影響;現在的硬體等級都很好,也許要經過很久才會出現問題,但因資源取得容易就認為可以隨便取用且亂丟終究是錯誤的行為。

保證每個元件處理好經手的資源,是維持系統穩定的必要條件;同樣地,人對於地球也應該抱持同樣的想法,才能讓生存的環境維持機能。至少,立即隨手把自己取用的物品放回原處,可以讓自己居住的場所保持整齊清潔,帶給身在其中的人較好的心情。

2007年7月26日 星期四

C14 View的設計(1)──起始與結束


這張圖是我所認定整個系統具有的完整層次。View的部分是以淡綠色所顯示的部分。

在架構設計的時期,第一步設計的對象是架構好上方所有的Interface,接著就思考每個部分的實作與對應使用的Component Interface,Component的實作則會在細部設計階段才需要處理。在這個階段,設計者應決定每一層Interface所應存放的位置,同時決定每一個功能在每一個Interface裡所對應的方法,以及方法裡應該作出的反應。

首先,在起動系統或是準備功能執行狀態的時候會有一個起動功能Controller負責產生顯示給使用者操作的畫面。畫面大多會以GUI(Graphic User Interface)呈現,裡頭必須包含功能所需之資料對應欄位(有些資料是經由計算或另外擷取的)。絕大多數的View只會有一種,這時可以省略掉Interface的定義;但是如果系統打算採用Multi-Channel的設計,就必須定義介面提供更換不同種類的View來存取畫面上的資料。

設計任何一個層次的時候,除了起動時自身的生成,同時也要注意功能完成後擁有物件的釋放。重覆不斷地使用資源卻沒有完全釋放的功能,會造成硬體的Memory Leakage而逐漸佔用系統記憶體,終至記憶體不足以宣告生成物件而導致系統停擺。在網路上所搜尋到的教學網頁全部都是教大家如何使用物件,還沒看到過同時教大家如何正確地完全釋放物件。結束時釋放的原則是以生成的方法順序倒過來逐一實行。

2007年7月25日 星期三

C13 小小的改變,大大的費時

公司的系統需要使用其他廠商的硬體來擴充功能,因而要我設計一個元件來對應這些功能。需求是在畫面上操作某些動作時,把資料變成XML字串送給其他系統,回應的結果再顯示到畫面上。

一開始沒想太多,只把送出的XML作成Model物件,Controller裡就直接在需要顯示的地方使用View的物件(公司的View經過包裝,使用自行開發的API),只用了幾天的時間很快地把功能做好並測試完成。過了幾天,主管說這個元件希望可以單獨銷售使用,View的部分希望可以同時支援標準的swing,為了做出這個功能我花了四個小時把Controller裡的View拆出並定義Interface。

直接把Controller與View分開並定義介面所花的時間應該只比原先直接使用的設計多一個小時,但是我花了整整四個小時做這個動作。而且在拆解的過程裡程式碼大量地被分割,連帶地所有功能全部要再重測與修正,總共用了六個小時左右,實在是得不償失。

類似的經驗在維護列印的元件上。當初寫這個功能的人看需求只要使用一款印表機,所以在列印的邏輯裡直接使用印表機物件;現在因專案的緣故必須支援三款不同的印表機,為了重覆使用印表邏輯,我必須花很大的功夫拆出邏輯與印表機功能物件並在其中定義介面。當然拆解後重新測試元件全部功能的動作還是免不了的。

當你多遇上幾次這種重拆重測的事,浪費許多時間重做之後,如果你是那個負責拆解與維護的人,相信你會更明白為每一個層次定義介面的重要性。

2007年7月24日 星期二

C12 應付變化的設計──Design Patterns

系統設計時常有需要變化的地方,累積了多次的開發經驗後,有人統計分析出一些較常見且需要的變化,並且設計出解決對應問題的機制,這些機制被稱為Design Patterns。

Design Patterns是很熱門的話題,搜尋一下網路可以找到許多相關的網頁。Design Patterns集結了前人的智慧解決系統設計上常見的需求,讓系統更具有抽換元件的彈性,但是增加彈性的代價是使用更多的Class來達成功能。因此也有Anti-Pattern的聲浪出現,希望設計時不要動不動就套用Design Patterns而造成設計上的負擔。

在應該變化的地方才使用Design Patterns是目前較中肯的使用時機,分析使用者所有需求的內容來決定哪些地方該使用Design Patterns。這樣的想法是合理的,如同記錄的內容需要平衡,設計的內容也同樣需要平衡;但是需求都可能變更,沒有人知道現在確定不變的事在未來會不會永遠不變。

我所採用的設計原則是:確定會變的地方套用Design Pattern,有可能會變但現在不變的地方定義Interface而不使用Class。設計時定義Interface再實作所花的時間大約是直接使用Class的三倍,但是把已經寫好的直接使用Class原件拆出Interface與實作差不多要花直接使用Class的九倍時間,而且還要另外再加上重新測試的時間。

就像記錄應該記下所有的事,但是內容應該簡單不要花太多時間,在設計的時候也應該為所有應該切割的地方定義介面預留未來的改變,但是不需要全部都套用Design Pattern。

2007年7月23日 星期一

C11 小朋友的親屬稱謂表

低年級的小朋友前陣子在苦背親屬稱謂表,一大堆叔、伯、姑、嫂、公、婆之類的名稱一下就讓小朋友頭昏腦脹,更何況還得去記得“爸爸的爸爸是爺爺“,”媽媽的姐姐是阿姨“等等這樣的人物關係。

教了幾種關係後,發現小朋友已經無法再多記,同時也感覺這樣的記法實在太耗費腦力;轉念一想,這個問題好像可以用OO的理念來解決耶。“人”肯定是獨立的個體,“稱謂”不正是從自己看出去後人與人之間關係總計後的結果嗎?只要定義好“人”這個類別,同時適當地決定與其他“人的關聯,那麼從關係求得”稱謂“是順理成章的。

“人”都有一位父親與一位母親(在這裡不討論例外情形)的長輩關係,同時有兄、弟、姐、妹四種同輩集合、子與女兩種晚輩集合、以及一位配偶關係。接下來從自己開始擺好課本所教的層數再拉上與人的對應關係,往長輩一層叫“父母層”、兩層是“公婆層”;父親的兄弟姐妹層的展開是伯、叔與姑姑,他們的配偶稱為伯母、叔母與姑丈;母親的兄弟姐妹層展開是舅與姨,他們的配偶稱為舅母與姨丈,依此類推……。

原先只能硬背的親屬關係在適當地切割獨立元件後,成為有條理可依循的關係圖表,相形之下學習者的可接受度與理解能力大幅地提升了。明明是相同的東西,經過設計與規畫再加以適量的說明,就可以把整個觀念傳遞給原先不清楚的人,這也是我們應該努力達成的方向。