2008年3月31日 星期一

M25 Package、Class、Method與Data的使用想法

系統的Use Case流程與Activity間的作法與Interface Method內的流程及動作間的作法是完全一樣的,重點都在於將需求定義出分解動作,然後另外使用流程串連起來以完成功能。把動作與流程分離,同時每個相同的動作都抽取到一個動作方法來呼叫是設計時的重點;另外考量的重點是動作方法放置的地方,要在同一個Class、同一個Package或是另外的Package需要考慮好。

當實作功能所有的過程都被拆解成Method後,應該要為每一個Method作定位。定義每個Method所處的Package、所處的Interface,思考時除了系統層面的Module定位(水平)之外,還要再考慮重用部分的定位(垂直),有的時候Method必須另外放置在特性描述的Interface。唯有把Method放置在多維空間的正確位置,才能在面對不同方向的切割時還可保有應有的功能。

Method裡使用的Data擺放也是需要定義的。在內部用完即丟的Local Variable、記錄Class狀態的Class Attribute、聚集成Package專用的Model Class或是定義在系統資訊區的Package,根據實際使用的狀況決定好放置的區域,才可以在適當的時候作正確的切割。(常數也是需要定義的Data)

reuse的想法除了把相同用途的Method或Data提取出來(同時要放置在應在的Package與層次)之外,也要包含在呼叫Interface Method時傳入參數就能變更內部處理流程的設計(需要定義更多類型的動作Method來支援);但是唯有盡量發揮這兩種設計特點,才能讓系統有更理想的最佳化。

2008年3月30日 星期日

M24 系統軟體架構的層次(5)──瞭解本質後定義自己的設計準則

如果曾經接手過別人寫的程式,大多數的情況裡都會覺得很難看懂,這是因為風格的不同所造成。由於軟體結構切割觀念、類別封裝設計原則、執行方法層次認知與使用資料建立擁有的差異,使得我們不容易理解別人的想法。

這段日子公司正在推行CMMI制度,可是CMMI對於設計僅僅規範了 SA/SD應該有哪些描述用的文件,卻沒有規範設計產出的風格;在這個情況下無論文件作得再多,也難以讓別人知道系統內部的設計。所以有機會跟主管聊天時,我都會強調先規劃好使用的設計方法論以及設計模型風格的重要性;唯有全公司的人都有類似的設計想法,才能夠快速地接手其他人的設計產出!

我強調的是Package、Class與Method的設計都遵守一對一的對應,根據相同應用所使用的資源都要抽取到固定的地方。“分層對應,動靜分離;絕對重用,靈活替換”這十六個字所表示的四個原則,濃縮了我這兩年接觸OOAD後最終的心得。

看過的設計文章都是以Class為基本單位來闡述,但是在瞭解本質後發現Method才是系統功能的主角;至今為止,覺得自己想出來的設計概念與別人大不相同。未來如果有機會在別人的系統裡看到與自己想法相近的設計,應該可以合理懷疑是受到我部落格文章的影響吧?

2008年3月29日 星期六

M23 Method vs Method、Data的追溯關聯

至今為止我們應知道Method才是系統執行的靈魂,追溯表的根本其實是Use Case對Method的追溯,中間使用到的Activity、Package、Class等等都只是為了表彰分層負責與功能集中的中間產物。不過直接從Use Case拉出關聯到Method將會是非常繁雜且龐大的過程,因此還是將系統切割多個層次,分開表達每個層次都應有的資訊與追溯較為簡潔。

在這裡提的Method理論上應該要是所有的Method,不過最小的限度可以定義為Interface Method。對於一個Method而言,對外應該要知道它被宣告在哪個Interface,輸入輸出的資料以及拋出的例外類型;對內則應該知道它執行時呼叫了哪些方法,以及使用哪些Class Attribute與外部的資訊。

之前提過Method本身擁有大量的資訊,是記錄時的重點,如今的記錄必須再加上Method使用其他Method與Data之間的關聯。Method vs Method的水平追溯表,Method vs Data的垂直追溯表,系統裡大量的Method與Data要怎麼做才能夠記錄必要的資訊,又不致於造成過多的額外工作,真的是一個很重要的課題。

我認為製作時必須分為兩層:每個Interface所對應的Class要做一次與其他Class的關聯(Class層級),每個Package Interface也要製作一個Package與其他Package的關聯(Package層級)。

2008年3月28日 星期五

M22 設計的製程(5)──決定產生與擁有資源的方式

這裡所謂的資源,其實是另外抽取到其他Class放置的Method或是Data。在這裡需要決定每個Class是如何生成的、如何保存以及如何取得與置換;如果這些外部的Class都有定義Interface,就可以應用多形的特性來靈活地替換實際使用的Class。

建立的方式通常有從建構子傳入、從方法傳入、建立固定類別與動態建立類別等四種;擁有的方法通常有直接擁有物件、使用集合存放與從其他物件取得等三種;在動態生成物件的場合,物件的Class Name可以從外部傳入或是由設定檔取得。

Method本身就具有大量的資訊,與所擁有的資源(其他Class)之間也有需要記錄的資訊。每一種佈置一定都有其原因與用途,因此在Attribute上需要註記的說明也是不可或缺的。註解的記錄方式可以在Attribute上加上Java Doc,除了可以直接出現在產出的說明上之外,還可以經由Reverse回歸到Rose Model裡。

使用在不同Class或Package的Method時,宣告Interface再應用彈性的設計生成Object,彈性設計指使用的實體由外部傳入,或是傳入指定名稱再由內部生成實體。

2008年3月27日 星期四

M21 彈性地擁有Method與Data──再看Design Pattern

有專家歸納出目前常用的Design Pattern共有23個,各位仔細觀察的話會發現裡頭用UML描述的全是Interface與Class。實際上做事的是Method,擁有資訊的是Data,但是這兩者並不會被直接拿來定義Design Pattern,而且群組化Method與Data的Class;如果這個事實還無法令人想更加改善Class的設計,我也無話可說。

Design Pattern用另一個說法來看的話,可以說是設計者想根據某種用途彈性取得Method與Data的設計,內部的作法是經過許多次實現而得到的最佳經驗值。取得與置換資源的方式有很多種,Design Pattern由於有很多人改善過將會是比較理想的作法;將之視為非做不可的設計而大量使用倒也不必。

Design Pattern的應用有三個分類,下面是所有Design Pattern與其分類;詳細的作法請另外參考專門的介紹。

生成模式(Creational Patterns),Class生成時可依其生成模式選用。包含有Abstract Factory、Builder、Factory、Prototype與Singleton五種。

結構模式(Tructural Patterns),為特殊的目的在結構方式安排Class的關係。包含有Adapter、Bridge、Composite、Facade、Flyweight與Proxy六種。

行為模式(Behavioral Patterns),為了達到指定的功能而將Class再拆解為更小單位,並依其關係結合成形。包含有Chain of Responsibility、Command、Interpreter、Iterator、Mediator、Memento、Observer、State、Strategy、Template Method與Visitor。

2008年3月26日 星期三

M20 設計的製程(4)──設計Method使用的Data

Data是Method在進行時所需要的輸入或輸出資訊,就像是我們在做事時分解出來的工作項目有其需要的資源般,在程式裡時常需要將這些資訊的處理結果保存下來供之後的方法使用。

在Method裡即用即丟的Data稱之為Local Variable,它們的存在只是Method裡暫時宣告出來放置短暫的階段性資料;只要是每次進入時都能夠由其它Data演算出來的,都會以這個形式宣告。(Local Variable可以視為reture value傳回)

處理結果的資訊如果必須保存下來提供給下一次呼叫Interface Method參考的話,就應該把它宣告在Abstract Class或Class裡,根據不同的方法劇本來存取它的內容。在我的設計裡,Class Attribute通常都會宣告為protected以便在繼承它的Class裡直接取用。如果允許外部取用該Class Attribute的話,一定要另外再宣告getter與setter。同時Class必須在initialObject()與disposeObject()裡管理所有Class Attribute的生成與消滅。

Package層級也有放置階段性資訊的地方,此時佈置的是稱為Data Model的Class;再往上一層,系統會有在執行時放置相關資訊的地方,通常也會定義一個程作Context的Package作為系統資訊存放用。如何取得Data Model或是Context就要視系統如何設計,不過在系統限制只能有一份Data Model或Context的時候,可以使用Singleton Design Pattern來放置。

2008年3月25日 星期二

M19 讓Interface裡的一對一對應無所不在

在與大陸程式設計人員合作的時候,發現他們對於Interface有著相當好的概念。有幾次想要偷懶,認為同一個Package裡的Class直接使用就好,還有在Method上想直接用一個getProperty()來省略一堆的getter方法時,他們都會提出要一個一個全部應定義好的建議。

他們的觀念是正確的。每一個物件的存在,都應該至少有一個對應的Interface來描述該物件在某個方面所應該要具有的功能;而每一個獨立的功能,也同樣應該要有一個專屬該功能對應的方法。我已經有很多次將多對一的對應,切割為一對一的經驗了,每次把一堆粘黏在一起的想法重新釐清都很辛苦,所以現在都試著努力讓自己的設計與系統的功能形成一對一的對應。

一對一的對應相對的影響,會造成Java File的數量增加不少。在前面的那個專案裡,我們一共宣告了113個Interface、40個Abstract Class與81個Class,這數目與一般直接宣告Interface與Class的作法(通常會有是一個對一個,81個Interface與81個Class)多了幾十個Interface與Abstract Class。

或許有人認為檔案數的增加相對地更難理解系統,但是採用直覺式寫法的原有系統即使說明一週還是難以上手,而我現在有把握只有說明半天就能讓新進維護人員有超過前者的系統概念。能夠快速地讓新進人員上手,都是因為所有物件有依照規律放在該在的位置、同時處理它該處理的功能。

註:一對一的對應,指一個Interface只為了一個目的而存在,一個Method只為了一個動作而存在。雖然定義上較為繁瑣,但是易於切割與使用;有時候也會將幾個固定動作的Method以特定邏輯包裝為組合型的Method,不過這應該特別註明包裝了哪些動作Method。

2008年3月24日 星期一

M18 系統軟體架構的層次(4)──各類Method的佈置與可見程度

在一個Interface實作的Class裡所使用到的Mehod,在類型上主要有入口、流程與動作三類;不過在使用層面上會依據以下各個情況來定義Method的修飾字:(這是我自己使用的方式,適用在Abstact Class)

Interface的實作方法就依宣告使用public。倘使這個Interface不希望給其他Package使用,在宣Interface前不要使用public,但是Method還是使用public宣告。記得在其他物件使用時務必呼叫在Interface裡有宣告的方法以維持入口的統一。

在實作時額外定義的方法,基於讓所有繼承它的Class具有該特性,都應該宣告為protected。如果很肯定這個Method只是單純在Abstract Class自己的層級使用,可以直接宣告為private;如果該Method有可能會被Class覆寫但是現在沒有,應該宣告為protected final,已被任一個繼承Class覆寫過的Method則僅使用protected。(final的定義同樣適用在Interface Method的實作)另外,在Abstract Class還無法決定要如何實作的Method,應該都加上abstract的修飾讓它能夠立即被看出將會在繼承的下一層實作。

雖然在Eclipse裡有Ctrl+T的功能可檢查指定的方法被哪些Class所覆寫,但是那必須得一個一個方法去檢查。方法的修飾宣告與其亂定一通,倒不如應用一點技巧來讓所有人從Outline就能看出所有方法的大致結構,這不是相對地方便很多嗎?

2008年3月23日 星期日

M17 令人感到麻煩的設計(13)──Method的起始與結束保持唯一

Interface Method的進入點對於實作的Class是固定的,執行的流程都從這裡開始。在流程中如果遇到要提早離開方法的狀況,相信很多人都會直接return或是throw new Exception()。

執行效能是很常被要求測量的,使用者會希望知道每個Use Case Method或是Activity Method執行需要多少的時間?直覺的想法當然是在進入方法時記錄當時時間,結束時再取得時間減去開始的時間就是答案。可是,哪裡才是方法的結束?看起來到處都有結束點,而且出去後就是分散到各個使用方法了。

基於“設計時預留抽取相同作用的地方”的想法,在方法外包裝一個入口方法來控制執行進出就可以完美地解決問題,無論流程在任何時間點return都會先回到入口。再往上一層想,整個系統進出入口方法裡通常會插入固定的事情,可不要直接把要做的事放到入口方法裡,還是依抽取的原則準備一個Class來放置進入入口要做的Method與離開入口要做的Method,在入口方法的前後則固定呼叫這裡的Method。

2008年3月22日 星期六

M16 流程與動作Method分離的好處

在把功能寫成一大塊的時候,在講解與交接時其實都不是那麼容易,再加上Method切割的標準不一,更使得寫出來的程式難以交給其他人處理。根據功能需求把Method切割為分解動作與串連流程除了寫的人多做一點之外,剩下的幾乎全是好處。

在開發方面,厲害的人可以專心針對某個動作找出最佳化的作法,只需要確認功能上的要求都有做到而不需要顧及流程的邏輯控制。另一方面讓能力普通的人協助流程的實現,只需要判斷程式流程是否依照傳回值走向正確的地方,而不致於碰上技術上的困難。適當的分工會讓每個人的能力得到更有效率的發揮。

在外包方面,如果動作牽扯上設備技術的機密(像是特別連接的週邊,或是特別的作法),我們可以把動作Method留在公司內部,把流程外包出去;反過來如果在專業領域的流程經驗值才是公司的資產,那麼就留住流程Method,把動作外包給別的公司做。

在維護方面,列示出功能需求的入口後,很容易可以找到流程的實作,從流程上先了解實現功能的概念,然後再依順序進入一步步的分解動作研究。條理分明地逐步瞭解,肯定會比一次把全部的東西的一大包丟過來要好,更何況還會在每一步驟上記錄註解。

2008年3月21日 星期五

M15 入口、流程與動作的沾粘現象

至今為止我覺得程式設計最需要遵守的原則,是把相同目的的程式碼抽取到共同的地方,而且應該只有一份。短短的一句,但是如何切割才是理想的共用部分?如何佈置才讓所有人員都很快知道應該共用的東西在哪裡?

流程與動作是應該分離的部分,程式之所以難改大多是因為數個動作被寫成一個方法,後來卻有修改那個方法的需要(尤其是在那數個動作間要插入其他動作時)。這個時候若沒有正確的追溯,就無法清楚在其他地方如何使用這個方法,無法兼顧所有使用而只看著眼前的需要去修改,就很容易造成其他使用同樣方法的功能問題。許許多多side effect(改好這個卻冒出別的)與defect repoen(已經改好的問題又再度出現)等讓使用者無法忍受的嚴重現象就會跟著發生。

對於Method功能需求的實作完全由流程與動作囊括,但是在什麼地方使用是由入口來決定;以這個現象來看,入口與流程應該是不同角度的思考。想像在使用者按組合鍵驅動某功能的場合,如果這個功能的流程寫在組合鍵的listener裡頭,如果使用者想要在按某按鈕時也要驅動該功能的話,勢必得要切開程式碼作些調整或是copy-paste了。

有個系統把F1到F12的功能鍵寫為獨立的listener,每個方法內都直接實作了特定的系統功能。後來同樣的系統在另一個客戶那邊使用,可是有些按鍵的對應功能需要改變,結果在這種情況下只能另外複製一個Class並且搬移“要變更對應的所有程式碼“,再設定Class Name來整個替換使用。同樣的入口、同樣的系統功能,結果產出了兩個幾乎相同的Class。雖然結果符合所有客戶的需要,但是這樣的作法適當嗎?

註:方法的入口有兩種考量。一種是只提供一種作法,讓進入的變化在專案那裡處理;另一種則把變化放置在傳入的變數中,進入方法後由內部加上一層入口來控制要進行哪個流程。一般狀況下以第一種最多,但是我現在會選擇第二種。

2008年3月20日 星期四

M14 替代的作法(7)──Interface Method的入口、流程與動作

Interface Method的功能需求就像是一個待達成的目標;在思維上先有的應是動作、然後決定動作的所屬權責、最後再以流程串接起來,以這種方式完成一個指定的功能。

在理想的作法上,當然能夠決定出動作Method的名稱與所屬的Interface或Class的話,就可以像L14那樣在流程方法裡註記所有的處理順序,之後再找程式員依註解說明填入適當的Interface Method。這個部分曾經有一個想法:如果有UML工具支援使用API來製作Activity Diagram的話,我一定會寫個程式找出系統內所有有流程註解的方法,然後每一個Method的註解流程都自動產生出一張Activity Diagram;不過這短期內似乎不可能。

大多時候在流程裡我們會知道要做什麼,但是沒辦法確認由哪裡去達成指定動作。這個時候我們只能先收集這個Package所有想做的動作再加以分析歸類,再定義內部的Method或是另外的Package Interface Method;等決定好所有動作負責的方法後再用上一段的註解方式來說明。這時就很類似K12的作法。

至於為什麼要有入口,接下來會說明原因。

2008年3月19日 星期三

M13 設計的製程(3)──Interface Method的入口、流程與動作

分析與設計的界線大多以Activity Interface作為界限,如何去使用Activity Interface Method是依照客戶相關業務的流程所定義,屬於需求與分析的範圍,而Activity Interface Method所描述的動作要如何被實現則是屬於系統的設計。對設計人員來說,Activity Interface Method是設計的起點,Method內所做的事都要依照定義的規格來設計。

每個Interface Method都會歸分為入口、流程與動作,每一類都應被分割為獨立的方法以便用不同的方式來組合使用。入口是實作Interface Method的Class Method;流程的實作會保持在相同的Package裡(可能會另外獨立出Interface);動作方法要視它的範圍權責,看要與流程方法放在一起、獨立出Interface或者使用其他Package Interface(通常是Component Interface)提供的方法。

每一個動作方法其實是一個更小範圍的設計需求,尤其是使用Pakcage Interface Method時尤為明顯,此時同樣再切分為入口、流程與動作來設計。在靜態方面,所有的Method都應該被列出在Class裡;在動態方面,每一個流程方法都應該使用Sequence Diagram來描述動作的順序(能用流程圖當然更好),至少要用L14的註解方式註明處理流程。

Method的設計其實是一種遞迴的處理,每個動作方法都被切為三個類型,其中的動作類再細分為更小的三個類型,直到那個動作適合在那個Class裡完全做到為止。記得把Sequence Diagram的目的侷限在描述Package內部的處理流程與外部Interface Method的互動,就不會得到冗長而難用的描述文件。

2008年3月18日 星期二

M12 做人的方法(9)──不要只看自己需要什麼

人們在旅遊前都會考慮到出去後可能會臨時感到飢餓或口渴,就將需要零食與飲料放到背包裡帶去。解決可能的飢渴狀況是需求,攜帶食物與飲水是我們解決這個需求的作法,但是解決需求之後呢?有很多盛裝的容器就在解決的同時被隨意棄置,被期待由大自然去釋放,可是結果並不是這樣。

在很多時候人們會為了自己的需要而準備許多東西,我們可以列出為了什麼需求準備了些什麼,卻很少人注意使用之後那些資源是如何處理的,也更不用談使用後的資源會造成什麼影響。資源如何到自己手上、發揮了什麼用途、之後怎麼善後,這是它的完整生命週期。

人想要隨心所欲當然可以,想要什麼就弄到手,不要的時候就隨意丟棄,從自己的眼光來看當然沒什麼問題,因為想要的東西都拿到了。不過從旁人的眼光看時比較不會去看那人怎麼使用,而是他用什麼方式拿到東西,又是用什麼方式讓東西離開他的;也就是那人處理身旁資源的方式。

人養寵物的目的只是暫時的娛樂,希望寂寞的時候有牠陪;在太忙或不容許飼養時就會讓牠離去,很多人會轉送旁人,但是也有不少人就任意亂丟。寵物認定主人是牠一生最重要的人,一天的等待只為了主人回來後短暫的陪伴,即使僅是如此都能讓牠有很大的快樂;如果身為寵物,你能容許主人這樣輕易的拋棄嗎?

自私的人只看得到自己手上的東西,唯有以同理心去注意每個相關物件生與滅時,才會懂得它存在的意義。

2008年3月17日 星期一

M11 替代的作法(6)──Class遵循生成與消滅的Interface

對於一個Interface的實作,第一步要建立實作Class的結構;先在Class Diagram放置好Interface、Abstract Class與Class的結構再一次匯出到workspace裡,會是比較理想的作法。在製作Class Diagram的時候可以同時在圖表裡檢視繼承與實作的正確性,直接寫程式的話製作的只是單線動作,無法快速得到立體的結構。

接著放置好每個Abstract Class與Class的constructor()、initialObject(), disposeObject、並依規範呼叫super();這部分的基本型態可以另存文字檔再複製回來,修改constructor()的名稱就可以快速完成。最後在Class裡依建立時間點來呼叫initialObject()。這些Method檢查無誤後再reverse回rose model微調Class Diagram的位置。

finalize()是Java物件在回收前最後執行的動作,但是它是無法取代disposeObject()的,因為它必須確定無其他物件使用後才會執行,沒法主動作出釋放資源的動作。設計時養成在哪個Class生成資源就在哪個Class裡負責釋放才是良好的習慣;如果資源是由Factory生成並傳出使用時,得另外確認它的消滅時間點,並在那時呼叫資源的disposeObject()。

2008年3月16日 星期日

M10 BasicObjectInterface的內部作法

每個Interface的設計如果都要作成這麼複雜,負責的人應該沒多久後就會想離職;在某些確定不需要切開層次的用途時,還是可以作成一個Interface對應一個Class的最單純作法。會用複雜的作法,是期望每一層都可以像洋蔥一樣,剝去上一層後還是擁有屬於自己的完整功能與結構。

MyObject o = new MyObject();
當我們在Class放置這一行時,其實包含了宣告與初始化兩個動作;當宣告多了之後會弄不清楚有哪些有被初始化過,又有哪些物件內記憶了其他物件而沒發現。因此宣告時我會先宣告為null,new的動作則統一放置在initialObject()裡,同時在dispostObject()裡宣告使用物件的disposeOjbect()並將變數再設為null以保證資源的釋放。

Constructor()、initialObject()要先呼叫super()再處理自己該作的事,disposeObject()則要先做完自己該釋放的資源再呼叫super()。最後在Class裡視物件生成時資源宣告的時機在constuctor()或是另外的initial()裡呼叫initialObject()就完成了這部分的設計。這樣佈置另一個重點是:讓 Class不能被繼承就可以保證一個物件生成時initialObject只會被執行一次。

這是一個簡單的實例:
public Class PreferenceClass {
 protected String filepath = null;

 public PreferenceClass(URL filepath) {
  super(filepath);
  initialObject(); //視時間點呼叫; Abstract Class沒有這動作, 因會經由呼叫super而通過
 }

 public void initialObject() {
  super.initialObject();
  obj = new MyObejct();
 }

 public void disposeObject() {
  obj.disposeObject();
  obj = null;
  super.disposeObject;
 }
}

2008年3月15日 星期六

M09 設計的製程(2)──Class遵循生成與消滅的Interface

在我的設計裡定義了一個BasicObjectInterface的介面,系統裡的每一個Interface都必須繼承到,這也表示每一個Class都必須要實作到裡面的規管。裡面只規定了管理物件生成與管理物件消滅的兩個Method:
public void initialObject();
public void disposeObject();

我的目的是在佈置實作Interface的Class同時約定這兩個方法的執行內容,以保證每個物件在生成與消滅的時間點都必定會呼叫到繼承關係的Method,而且限制只呼叫一次,同時依使用時的關係與責任來消滅由自己建立的其他物件。下面是基本型態的Class佈置與initialObject()、dispostObject()的結構圖。

從最偷懶的一個Interface對應一個Class實作擴張為最基本要五個Interface、四個Abstract Class與一個Class,我相信任誰都不想多花精力去鋪陳這種設計,不過只宣告Class的空殼其實很快,尤其在放置Method的時候設計的想法與堆在一個Class是差不了多少。但是在這裡多花的一點時間,卻有機會換得彈性強、易說明、少side effect的系統,這是我在多花時間佈置後所樂意見到的。

2008年3月14日 星期五

M08 規範所有物件的生成與消滅

在執行Method的時候如果需要用到Class裡的物件,大家都知道要預先準備妥當,不管是事先將物件放置好或是由Method傳入;在消滅Class的時候大家都相信J VM的Garbage Collection,所以大多都只是把放置物件的變數設為null後便將之遺忘,任由執行環境將之回收。

然而執行環境也有回收失敗的時候,例如下面的例子:
// 在方法內生成物件的時候. target與list是Class內宣告的Object與List.
target = new Object();
list.add(target);
// 在target不用的時候
target = null;

在消滅target的時候只對它設置為null,卻忘記它在list裡還存放著reference,以致於target物件根本無法被回收;這種現象一多,執行的記憶體遲早會被佔用光而造成系統異常結束。此現象稱為Memory Leakage,是系統問題最難排除的一種,因為根本很難判定哪裡佔用了應該釋放掉的物件。

“需要的時候已經產生好隨時可用,不要的時候有沒有消滅掉都無所謂”,如果對於物件的管理只看重需要的時候,那麼其實是沒有秩序可言的。萬物的生滅應該有其道理,所以在設計裡應該也要將每個物件的生滅視為一種應該詳細定義的規格。

2008年3月13日 星期四

M07 每一組實作Class該做到的事

Package是概念上的範圍,完全沒有事情要做,只需定義Package Interface即可;不過Class是真正負責做事的集合,為了維持本身的正常必須多設計一些東西。因此佈置好Package內的Interface與Class的結構後,在開始設計Package Interface所定義的方法之前,要先定義Class存在時所應盡到的責任。

Class的存在就像一個人存在於世界上,會有出生與死亡的時間點與當時要做的事;而且其本身必須妥善管理自己擁有的資源,不管資源是在Class內部產生或是從外部傳入,只要Class內有屬性存放就必須加以管理。如果存放資源的屬性是集合類型(List、Map之類),管理動作還要涵括集合的管理。

Class建立時的行為、定義負責管理資源的屬性、定義資源管理相關的方法(提供外部操作的放到Interface)、Class消滅時的行為,以上四種類型動作的設計是Class可以正常運作的保證。

2008年3月12日 星期三

M06 設計的製程(1)──Package的內部結構

Package的結構圖是必要的,因為可以讓人很快地明白內部的結構,並表達Package內的佈置與垂直繼承的關係。因為每次直接看著project裡一堆Package以及展開後的一群Class總是有無從下手之感。

每個Package都要各自繪製一張Class Diagram,重點在傳達Package內部的物件結構,要包含內部所有的Interface、Abstract Class與Class。可以不用畫上其他Package的物件內容,因為使用的關係可以在Sequence Diagram裡找到。

Package內的每一個Interface、Abstract Class與Class都應該把他們實作與繼承關係的物件拉進圖裡。有個理論說正常人最多只能記得單一Class六層繼承關係,不過即使繼承層次不多,一次要弄清楚好幾個Class全部的繼承與實作關係卻也不是簡單的事。以L07的Interface結構為基礎,每個Interface都佈置出準備實作的Abstract Class結構,並且在專案專用的層次放置Class。下圖是根據L07附圖產生的一個基本Package Class Diagram。

2008年3月11日 星期二

M05 系統軟體架構的層次(3)──Package內的Class結構

Package內會有一個Class來實作Package Interface,如果要把入口、流程與動作放置在同一個Class裡的話,就只有一個Class安排在Package裡,再加上我認為必要的Abstract Class就是最基本的Package內部結構。

在D06裡有張Component的內部結構圖,可以拿來作為定義Package結構的內容。只看與Package Interface有關的部分,從左邊開始正好依序對應為入口、流程與動作;這三者分開不同層次置放是為了與做事的本質對應,各自定義了Interface則是可以使用映射或是Design Pattern予以動態置換。 即使Method在Package內被拆解為入口、流程與動作三個部分,但是每個部分都是獨立存在,一樣是需要設計的。下面的例圖是UI Bean內的controller依照UI實際特性佈置的結構,最左邊是Interface,最右邊一排是Class,其餘中間的全部都是Abstract Class。在我所做的系統裡每一個Interface(不管是入口、流程、動作或Data Model)之下的Class全都是作出這樣的設計。


2008年3月10日 星期一

M04 令人感到麻煩的設計(12)──不要使用Inner Class

在我的設計裡對於Inner Class也是禁用的,因為使用之後形成的是一種無可抽換的綁死關係;即使Inner Class實作了Interface也是一樣,因為就算使用時可以替換,但在實質上它是屬於這個Class裡的一部分。

另一個原因在於使用Inner Class後,無可避免地會在裡面直接使用Outer Class裡的屬性或方法,這時的Innter Class根本就是Outer Class的子集合而已。把Method從一個Class裡切割到兩個以上的Class是很痛苦的, 因為是把緊密相連的東西硬拉到兩個不同的個體裡……類似之前我誤把View與Controller寫成一個Class而必須拆開的痛苦,一有差錯就會失去原有的功能。

想像一下分割連體嬰時所花費的精力就能明白事先將該分開的物件切割是正確的,因此我完全不使用Inner Class的設計,而直接把Class獨立出來再於Method傳入需要操作的Class。

2008年3月9日 星期日

M03 令人感到麻煩的設計(11)──用Abstract Class作對應

接著上一篇提出的問題,從圖1的基本型態開始要加入另一個有7個方法不同的Class,不過這次採用並聯的佈置,讓它直接實作Interface,這會形成圖2的佈置。兩個Class有13個完全相同的方法,可是完全沒有地方可以放置相同的程式碼,這使得設計時“抽取相同部分”的動作無貫徹。

很明顯地在這個例子裡放置Abstract Class會是個好解法。(圖3)這種方式讓我們能夠把相同的13個方法放置到Abstract Class,再個別在Class裡分開實作那7個不同的方法;基於覆寫後可能的失控,能夠設計為沒有覆寫方式的話我都會採用該種作法。


常聽到Class是特性的定義、Object是Class實體的說法,不過在我自己的設計裡會調適為:Abstract Class是特性的定義、Object是Class的實體、Class只是為建立Object的需要而存在。儘量把共用的動作方法建立在Abstract Class裡,Class裡要放的只有該Class特殊化的方法,這時再看前一篇就能夠現在發現完全沒有繼承Class的必要,因為所有需要的動作全都在Abstract Class中!

這裡都是我自己所用的設計,會這麼做是因為能夠避免我所遇過的一些問題,但還是可以保有設計的功能性。

2008年3月8日 星期六

M02 令人感到麻煩的設計(10)──讓Class無法被繼承

Package Interface無論如何都會有一個Class實作,如果現在的需求明確地知道只有一種實作的可能時,相信大多數的人都會直接佈置一個Class,因為這是最快完成需要的方式。(圖1)此時我們先假設Interface裡有20個Mehod。

過了一陣子有不同的專案需求,其中7個Mehod需要改寫,為了快速的reuse,很多人會選擇直接繼承原有的Class後再覆寫那7個需求變更的方法。(圖2)再後來,又有一個新的專案需要改寫,但這次是前次變更的7個Method有兩個要與最原始那個一樣,這時一樣是繼承前一個Class再把2個Method從最早的那個複製過來。(圖3)


達到功能需求的變更這是最快的作法沒錯,但是有沒有覺得哪裡怪怪的?PackageImpl與PackageImpl_2裡有兩個方法是完全相同的;PackageImpl裡擁有的20個方法與PackageImpl_1擁有的7個方法沒法立即得知哪些被後面的Class覆寫。Class越多時同樣的狀況到處都有,恐怕連自己都會搞不清楚當初的佈置是怎樣。

雖然OO語言裡Class是允許被繼承的,但是為了避免這種混淆發生,我打算將所有Class都宣告為final,讓特性都繼承自描述用的Abstract Class。

2008年3月7日 星期五

M01 Class是類似動作Method加上使用Data的集合

Method是為了達成功能存在的,定義好系統的所有方法後如果沒有妥善分類,就會形成內部錯踪複雜或是到處都有類似的程式碼;Class是擁有Method實體並將之群組化的單位。

在定義Interface已經決定好有哪些類似目標的Method會被找出來並放在正確的位置,接著在每一個Interface之後都必然會有一個實作Class對應。Class上會有繼承與實作的問題,會依特性決定Class的“族譜”,再決定Method要在哪一層Class來實作,或者實作後要在哪一層因需要而改變。

Method在執行時通常會用到Data,執行時所用的Data在執行後不需傳回但需要保存留待後續呼叫的Method使用的話,就要定義為Class Attribute留存在Class的範圍內。在困擾的Method定位之後還有Data的設計,使用到其他層次的物件時的生成與管理……等等,Class這一層的設計是最煩雜的部分。

相對來說,Package並沒有任何實作而只是範圍上的定義,定義Package Interface會由哪些Class共同達成。

2008年3月6日 星期四

L19 系統軟體架構的層次(2)──Package的各種定義

簡單地用一張圖來表示Package使用上的定義層次,由左到右依序為範圍大到範圍小的,每一個物件都可以使用自己層級與往下層級的物件,除了Main Flow、Use Case會被限制只能存取到下一層。請記得每一個圖示都應該對應一個Interface的宣告,這是我偷懶而沒畫出來的。

MainFlow是使用者執行同時掌管Use Case是否進入的Package,Use Case與Activity對應的是系統分析時Use Case、Activity的進入點,,Module則是在Activity裡合作完成工作的各個組織。

MainFlow、Use Case、Activity與Module是屬於專案應該定義出來的部分,而Component與CommonAPI是屬於通用的部分所以列在下一行。後兩者同樣根據L07的reuse基準定義出不同層級使用的元件與函數。

2008年3月5日 星期三

L18 一層層地依序鋪陳系統結構

在作系統功能分析時,一開始會定義出與初始化系統有關的,因為我們得先準備好系統才能去使用;然後會接著定義使用者操作系統的相關功能,讓系統發生功用;再來會分析出操作系統的同時會不會產生對使用者回饋的系統事件;最後則依照需要提供給使用者定義的系統參數來定義參數的功能。

這是目前我在設計系統時採用的四段分析與設計,讓系統的每一層就像IC晶片的腳座與針腳,一層層地向上堆疊到最後得到完整功能的系統。前面說明的內容大致上只適用在第一層(初始化)與第二層(操作系統);如果系統有讓使用者註冊與客製化的事件或是客製化的參數,就還要分析那些功能應該放置在哪些Module,同時定義出它的詳細內容並記錄。

與客戶討論定義出來的Use Case其實會因分析的過程而有所增減,切記要把所有的變更都對應記錄在文件裡,只要少做了一個就會造成系統與文件的脫節,而且未來絕對很難被找到。當事人應告誡自己不要因偷懶讓少做的事造成文件的失真。

Use Case的變動也該與客戶溝通以取得共識,作適當的調整。雖然需求確認後系統會比較好做,但是我們在分析與設計時都時常發生遺漏與錯誤,又怎能期待客戶光是討論就可以得到完整的功能?為了讓系統隨時符合那些隨時會增訂的需求,還是在分析設計時作好一對一的對應吧。

2008年3月4日 星期二

L17 至今還只是設計Package Interface!

曾看過一篇文章,作者說到他根本不想學其他的方法論,因為只要給他資料結構就可以用DFD設計出系統!這是最快速做出系統的方式,因為只要把完成功能該做的動作全都做到就不會錯,也不需要去管Method要怎麼安排、Class與Package要怎麼佈置。

為了reuse,所以採用了提取相同部分的作法,從最小的動作(Method)開始、Method裡使用的固定數值直到集合型態的Class、Package等等都是可以切割並抽取的單位;為了彈性,每個切割的分隔還可以加上替換的機制(常數的判斷或Interface的宣告)動態更換其中一段的執行動作流程。

而到目前為止所做的工作只是Module層級的的佈置與系統分析相關的Method宣告,主要做的事是決定達成所有功能的全部方法,並將方法一一佈置到對應層級的Module Interface。由於Interface只是Method宣告的空殼,所以在review的同時可以快速地調整Module的佈置與Method的所屬interface;同樣的動作若等到Class與實作都出現時再動作,可就要再多花更多精力。

Interface後對應的是達成特定功能的Package,依照設計原則定義的Interface應會具有適合重用與維護的特性,也更適合再進一步套用Design Pattern增加彈性。這樣的概念也會延續到同樣是集合方法的Class,讓Interface的設計特性延續到更細部。

2008年3月3日 星期一

L16 Use Case與Activity的追溯關係

在Rose的Logical View建立Use Case Diagram後可以直接查詢到任一個Actor或Use Case的全部使用關聯,但是即使畫了Activity Diagram與Sequence Diagram也只能使用SoDA的條件篩選功能來查詢到Use Case對Activity以及Use Case Interface對Activity Interface全部的使用關聯,而沒法在Model上快速地直接查詢。

在定義Use Case Interface與Activity Interface的時候,如果明確地限制了它們所在的Package,就可以從程式碼快速地得到Use Case對Activity之間的使用的關係。

從Activity Method找使用它的Use Case Method時只要選擇Method Name再執行References就能夠得到所有使用它的Class & Method,只要留下Use Case所在的Package就是我們要的答案。反過來,從Use Case找使用的Activity時只要把該Use Case Method內所有與流程有關的方法內容剪賠到一個文字檔,就能找出所有的Activity。

在需要繳出水平追溯與垂直追溯的場合,可以使用Eclipse的JDT插件來分析程式碼。定義好Use Case的Package集合與Activity的Package集合,依序進入每一個Use Case Method一步步地列出其中定義在Activity Package的物件名稱與方法,就可以產出Use Case vs Use Case、Use Case vs Activity與Activity vs Activity三份追溯關係表。

這個工具搭配JXL產出為Excel檔案就是保證100%正確的追溯關係表,未來有任何程式碼的改動時只要再重新執行一次又能得到當時最新的結果。

2008年3月2日 星期日

L15 Interface Method需要記錄大量的資訊

Method是真正在做事情的單元,所以它使用的資訊也是最多的。Class與Package只能說是分類Method的集合標準,只會帶有它自己的規格說明與結構的關係,前者會成為標準的Documentation,後者則應該使用Class Diagram加以描繪。

在L11的Excel表格裡我們可以發現在Method的宣告就具有名稱、動作規格、傳入參數與傳回值等資訊,這些都必須一一詳加描述,才能夠讓接手的人做出符合期望的內容。其中的動作規格可以取得Activity Documentation轉到Method的Java Doc作為說明。

在方法的執行需要上,Use Case Interface Method需要Activity Diagram與Activity Interface Method List,而Activity Interface Method會在SD的階段才加以設計,但需要的內容也不外乎流程與動作的組合資訊。在Use Case Interface之外,會有開始執行該方法的時間點(Entry Point)、進入前的系統狀態(Precondition)與執行後的系統狀態(Postcondition),這些都必須在啟動系統的主流程裡加以考慮與設計。

以上所有提到的Method資訊應該全部都要說明的,但是大多數人會認為我全部都知道了為什麼要再浪費時間寫出來一遍?面對一個沒有說明的Method或許還能從少量的程式碼反推,但是面對一群沒有註解的Method時?再更慘一點,面對的是一大堆沒有分類好的Method散落在許多Class與Package時呢?其至連Class與package都沒有依據準則分類時,更是慘不忍睹。

想像一下新進人員與現在的自己對於系統認知的差距,那正是需要記錄的資訊。

2008年3月1日 星期六

L14 替代的作法(5)──Use Case的執行流程

Sequence Diagram當初對我造成不小的困擾,一部分是因為不確定它應表達的深度,另一部分是因為一張圖只選擇一個劇本作為表達;這令拿到全部Sequence Diagram的人不易明白Use Case的完整作法。

在流程裡所做的動作只有三大類:執行動作、設定資訊與取得資訊,我的作法是以這個三個基本分類來切分並定義所有Activity,並隨之對應到一個Interface Method。在沒時間製作畫面的時候,可以先定義好Use Case對應的Interface Method,然後每個方法都用一個文字檔案來儲存說明。文字檔案的內容可以像寫程式般把Activity Name或Activity Interface Method Name嵌在裡面,前面再用程式註解開頭。

例如:
// if (印表機進紙狀態 == 沒紙) {
// 印表機進紙
// }
// 印表機列印
// 印表機退紙

註解時盡量把流程控制的作法交待好,未來實作者的工作就是依註解替換為Interface Method,並驗證程式的執行邏輯是否正確。如此一來Use Case Method內就只是純粹的方法呼叫與邏輯控制,這樣也使得註解的效用等同於Activity Diagram,既帶有流程圖的意味,在寫程式時保留下來同時又能作為程式註解用途。