在設計與實作的時候,所有的固定數值與字串都應該被抽取為常數,使用的時候就將固定數值與字串置換為該常數。原本我也依這個方式使用常數,但是不久前製作計算function points工具程式時卻發現不同的用法。
上下傳電文欄位是功能點計算的來源之一,第一個專案的電文定義檔命名規則是在交易代號後加上u或d來表示,因此將上下傳檔名另外用常數代表,取檔時就使用txnNo+UPLOAD_FILENAME+".xml"來組成完整檔名。程式拿到第二個專案就發生了錯誤,因為檔名是以別的名稱識別,只好直接修改常數內容讓程式可以正常執行。
依照事對物的想法,有UPLOAD_FILENAME的存在就應該對應存在一個getUploadFilename()的事存在,之前在檔名裡直接嵌入UPLOAD_FILENAME的作法是一種綑綁。"取得上傳的檔名"應是一件事,其中包含的是一種規則,雖然在大多數的情況下規則是直接取得UPLOAD_FILENAME,但是規則總有變的一天,將改變的規則限定在一個給定的方法裡可以增加設計的彈性。
那個工具程式後來作了一個改變:收集所有在專案裡可能有所不同的規則,定義一個Interface容納取得那些規則的方法,看要執行哪一個專案的內容就使用該專案特有的規則定義。
得到了寶貴的經驗:每個常數都應該轉換為具有該層級元件意義的名稱與存取方法,以便操作時明白其含義。呼叫時可直接在參數裡引用常數,但是在取用時要使用存取方法,因為取用時隱含規則,規則都可能改變。
2009年6月17日 星期三
2008年4月11日 星期五
N11 令人感到麻煩的設計(15)──避免將參數放在設定檔
決定不用程式碼寫死使用關係的同時,表示我們必須在某個地方記錄對應的關係並在執行時取得。通常的最快作法是將參數放在properties檔案,再使用ResourceBundle方式讀入為Properties取用;不過使用時再多往幾個方向思考一下,或許會有不同的決定。
檔案位置的固定。很多時候為了方便設計與管理,Component的參數檔案會被指定放在一個固定的地方,而且通常是使用它的Package裡。這樣的放置會造成設定檔被“綁死”在那個地方,無法應付系統要求要集中所有的設定檔方便管理的需求。
檔案類型的固定。在設計上採用傳入檔案所在的URL後再於內部讀取內容,這樣的設計可以讓設定檔在任何存取得到的地方。不過取得的來源就只限於properties檔案,要是客戶另外有xml或是資料庫來存放設定的話,就沒法動態由不同的來源產生。
雙向讀寫的要求。即使設定檔由外部組成Properties物件再傳入Component的作法可以滿足以上的想法,但是Properties的特性是只能讀取的。設定一般來說是允許客戶修改的,直接修改檔案內容雖然快卻有修改內容發生錯誤的狀況,此時可能需要選用別的物件。
靈活替換的判斷資訊有很多存在於設定檔,針對以上各方面的考量,在設計上我會定義自己的設定檔物件,由客戶決定怎麼生成Component需要的設定來滿足以上的想法;當然,這個設定檔物件是存放在元件庫裡的。
檔案位置的固定。很多時候為了方便設計與管理,Component的參數檔案會被指定放在一個固定的地方,而且通常是使用它的Package裡。這樣的放置會造成設定檔被“綁死”在那個地方,無法應付系統要求要集中所有的設定檔方便管理的需求。
檔案類型的固定。在設計上採用傳入檔案所在的URL後再於內部讀取內容,這樣的設計可以讓設定檔在任何存取得到的地方。不過取得的來源就只限於properties檔案,要是客戶另外有xml或是資料庫來存放設定的話,就沒法動態由不同的來源產生。
雙向讀寫的要求。即使設定檔由外部組成Properties物件再傳入Component的作法可以滿足以上的想法,但是Properties的特性是只能讀取的。設定一般來說是允許客戶修改的,直接修改檔案內容雖然快卻有修改內容發生錯誤的狀況,此時可能需要選用別的物件。
靈活替換的判斷資訊有很多存在於設定檔,針對以上各方面的考量,在設計上我會定義自己的設定檔物件,由客戶決定怎麼生成Component需要的設定來滿足以上的想法;當然,這個設定檔物件是存放在元件庫裡的。
2008年4月9日 星期三
N09 令人感到麻煩的設計(14)──Component間的避震器
Component更應該要堅守使用專用Data Model的原則,因為Component之上是各式各樣不同的其他元件以及系統。只要兩個以上的Component使用了同一種Data Model,即使二者之間並沒有直接呼叫對方的Method,兩者還是產生了間接的使用關聯。
如果在設計上它們不應該產生使用同一種Data Model的關係,那麼應該先讓兩個Component使用各自的Data Model,並另寫一個資料轉換的函式負責在兩種不同的Data Model間搬移內部的資料,或者是在系統裡準備一個共用的系統資料區,在全部需要準備Component Data Model的情況下都與系統資料區作資料的交換。在開發系統時我比較偏好後者,縱使需要為每個Component撰寫一個資料轉換程式,卻可以保證在同一系統下是同一種作法。
在動作方面,可能某一個系統的動作需要同時使用數個Component的方法,直接依序把Component Method放到系統動作方法裡是直覺的設計。但是在分析階段時,必須要檢視是否有一個系統動作需要同時執行數個Component Method的狀況,如果有的話還是另外抽取一個系統方法存放為佳。
無論是Method或是Data,在有差異存在時務必要安排一個物件(而且只有一個)來負責填補其間的差異,以期在使用時能夠直接呼叫而感覺不出有差異。
如果在設計上它們不應該產生使用同一種Data Model的關係,那麼應該先讓兩個Component使用各自的Data Model,並另寫一個資料轉換的函式負責在兩種不同的Data Model間搬移內部的資料,或者是在系統裡準備一個共用的系統資料區,在全部需要準備Component Data Model的情況下都與系統資料區作資料的交換。在開發系統時我比較偏好後者,縱使需要為每個Component撰寫一個資料轉換程式,卻可以保證在同一系統下是同一種作法。
在動作方面,可能某一個系統的動作需要同時使用數個Component的方法,直接依序把Component Method放到系統動作方法裡是直覺的設計。但是在分析階段時,必須要檢視是否有一個系統動作需要同時執行數個Component Method的狀況,如果有的話還是另外抽取一個系統方法存放為佳。
無論是Method或是Data,在有差異存在時務必要安排一個物件(而且只有一個)來負責填補其間的差異,以期在使用時能夠直接呼叫而感覺不出有差異。
2008年3月23日 星期日
M17 令人感到麻煩的設計(13)──Method的起始與結束保持唯一
Interface Method的進入點對於實作的Class是固定的,執行的流程都從這裡開始。在流程中如果遇到要提早離開方法的狀況,相信很多人都會直接return或是throw new Exception()。
執行效能是很常被要求測量的,使用者會希望知道每個Use Case Method或是Activity Method執行需要多少的時間?直覺的想法當然是在進入方法時記錄當時時間,結束時再取得時間減去開始的時間就是答案。可是,哪裡才是方法的結束?看起來到處都有結束點,而且出去後就是分散到各個使用方法了。
基於“設計時預留抽取相同作用的地方”的想法,在方法外包裝一個入口方法來控制執行進出就可以完美地解決問題,無論流程在任何時間點return都會先回到入口。再往上一層想,整個系統進出入口方法裡通常會插入固定的事情,可不要直接把要做的事放到入口方法裡,還是依抽取的原則準備一個Class來放置進入入口要做的Method與離開入口要做的Method,在入口方法的前後則固定呼叫這裡的Method。
執行效能是很常被要求測量的,使用者會希望知道每個Use Case Method或是Activity Method執行需要多少的時間?直覺的想法當然是在進入方法時記錄當時時間,結束時再取得時間減去開始的時間就是答案。可是,哪裡才是方法的結束?看起來到處都有結束點,而且出去後就是分散到各個使用方法了。
基於“設計時預留抽取相同作用的地方”的想法,在方法外包裝一個入口方法來控制執行進出就可以完美地解決問題,無論流程在任何時間點return都會先回到入口。再往上一層想,整個系統進出入口方法裡通常會插入固定的事情,可不要直接把要做的事放到入口方法裡,還是依抽取的原則準備一個Class來放置進入入口要做的Method與離開入口要做的Method,在入口方法的前後則固定呼叫這裡的Method。
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。
另一個原因在於使用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中!
這裡都是我自己所用的設計,會這麼做是因為能夠避免我所遇過的一些問題,但還是可以保有設計的功能性。
很明顯地在這個例子裡放置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。
過了一陣子有不同的專案需求,其中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年2月16日 星期六
K16 令人感到麻煩的設計(9)──把固定數據提出成Constant
在程式寫作時常會有一個要求:要求把所有放置固定值的地方都另外使用常數來放置。對於寫作的人來說這是感到麻煩的地方,因為明明可以直接放置固定不變的值卻變得必須額外指定一個常數,無形中浪費了一點時間,而且有時無法快速找到定義的內容。
很多額外多做的行為都是為了一個目標──把相同意義的東西抽出到同一個地方。異中求同是在這裡是必須具備的能力,大至從Use Case裡把相同子劇本提出的sub Use Case、佈置系統架構的Module、為完成特定功能的Component、Class特性的分析與定義、決定完成指定功能的Method、直到將使用的Constant拉出到通用的變數,每一個提取動作的目的都是為了“完整的reuse”。
在遇到第一個可能會被reuse的部分時,會有人因為想少做點事而沒作提取的動作,但在接連遇到同樣的情況出現後,還是無法避免去做;而且等到後面才做時,必須得找出之前沒有提出的地方做出少許破壞程式改用共同部分的舉動,感覺上像是把之前沒做的事堆在後頭一次做完,而且可能會有漏失的風險。
所以我的作法是:該做的地方就在遇到的時候處理掉,即使它可能只出現一次。
很多額外多做的行為都是為了一個目標──把相同意義的東西抽出到同一個地方。異中求同是在這裡是必須具備的能力,大至從Use Case裡把相同子劇本提出的sub Use Case、佈置系統架構的Module、為完成特定功能的Component、Class特性的分析與定義、決定完成指定功能的Method、直到將使用的Constant拉出到通用的變數,每一個提取動作的目的都是為了“完整的reuse”。
在遇到第一個可能會被reuse的部分時,會有人因為想少做點事而沒作提取的動作,但在接連遇到同樣的情況出現後,還是無法避免去做;而且等到後面才做時,必須得找出之前沒有提出的地方做出少許破壞程式改用共同部分的舉動,感覺上像是把之前沒做的事堆在後頭一次做完,而且可能會有漏失的風險。
所以我的作法是:該做的地方就在遇到的時候處理掉,即使它可能只出現一次。
2008年2月15日 星期五
K15 令人感到麻煩的設計(8)──每個設計項目有各自的Data Model Interface
曾經接觸過一個系統,裡面的Data Model實作是一個巨大的Map,它提供了兩個最單純的存取方法:getData(String name)與setData(String name, Object value)。系統裡所有需要存取Data Model的地方只都經過這兩個方法。
這在存取的時候既方便又好學,作出來的功能也不會有什麼問題,但麻煩來自於後面的一個需要:有個專案有其中一個模組的需求,詢問是否能夠reuse我們設計好的模組。我回答不容易,因為這個Data Model沒有設計Interface所以使用的模組只能用它,而且沒有根據Module來定義它所能存取的Data,變成必須要去追所有程式碼才能找出用到哪些名稱的Data。最後當然沒人要去作這種追溯而情願重新寫一個。
這就是沒有Interface並加上多合一設計的糾結。沒有定義介面變成使用時一定要依賴該Class、沒有定義該Module專用的Interface就難以快速得知它使用的資料範圍、入口的合併造成不容易分辨執行時要存取什麼資料;Data Model像是無形的鎖鏈, 將明明分開設計的Module綁死在一起。這使得要記得Interface是一個規格,定義時以能夠只觀看Interface就能明白它提供了什麼樣的功能為目標。
這在存取的時候既方便又好學,作出來的功能也不會有什麼問題,但麻煩來自於後面的一個需要:有個專案有其中一個模組的需求,詢問是否能夠reuse我們設計好的模組。我回答不容易,因為這個Data Model沒有設計Interface所以使用的模組只能用它,而且沒有根據Module來定義它所能存取的Data,變成必須要去追所有程式碼才能找出用到哪些名稱的Data。最後當然沒人要去作這種追溯而情願重新寫一個。
這就是沒有Interface並加上多合一設計的糾結。沒有定義介面變成使用時一定要依賴該Class、沒有定義該Module專用的Interface就難以快速得知它使用的資料範圍、入口的合併造成不容易分辨執行時要存取什麼資料;Data Model像是無形的鎖鏈, 將明明分開設計的Module綁死在一起。這使得要記得Interface是一個規格,定義時以能夠只觀看Interface就能明白它提供了什麼樣的功能為目標。
2008年2月13日 星期三
K13 令人感到麻煩的設計(7)──入口、流程與動作
Use Case與Activity的製作其實就是最high level流程與動作的分離,藉由將Activity定義為Interface Method將之固定為完成特定動作的目的,此時的Use Case可以被限制到只是用流程串連必要Activity的規則。
當然我們可以把Use Case用一個Class甚至是一個方法來完成,在功能上所有作法得到的結果都是一樣的,但是把多個動作組合在一個方法裡所付出的代價就是它被限制住只能有組合後的結果。雖然加上參數後可以應付多種不同的需要改變內部,但是當出現要增減動作的需要時就會發現改動不僅不容易而且還要考慮很多相容的問題。這是把流程與動作合併在同樣方法的問題。
雖然重點在於流程與動作的分工,但是入口卻是一個容易被忽略的地方。即使在很多地方很明白地表示進入後就是要達成某件事,不過仍有可能會在進入後參考某些設定而改變要做的事;而有時會只需要在其他地方處理同樣的功能,倘使把流程跟入口綁在一起又會形成再加工切割的後果。我們必須記得的是:預留好事後切割的空間會比重構結構來得容易而且少很多風險。
在每一個特定功能的設計裡,我的作法都會是:使用入口(有時的確不需要存在)、流程與動作三個層次合作完成指定的功能。(D06的圖也是類似的概念)
當然我們可以把Use Case用一個Class甚至是一個方法來完成,在功能上所有作法得到的結果都是一樣的,但是把多個動作組合在一個方法裡所付出的代價就是它被限制住只能有組合後的結果。雖然加上參數後可以應付多種不同的需要改變內部,但是當出現要增減動作的需要時就會發現改動不僅不容易而且還要考慮很多相容的問題。這是把流程與動作合併在同樣方法的問題。
雖然重點在於流程與動作的分工,但是入口卻是一個容易被忽略的地方。即使在很多地方很明白地表示進入後就是要達成某件事,不過仍有可能會在進入後參考某些設定而改變要做的事;而有時會只需要在其他地方處理同樣的功能,倘使把流程跟入口綁在一起又會形成再加工切割的後果。我們必須記得的是:預留好事後切割的空間會比重構結構來得容易而且少很多風險。
在每一個特定功能的設計裡,我的作法都會是:使用入口(有時的確不需要存在)、流程與動作三個層次合作完成指定的功能。(D06的圖也是類似的概念)
2007年9月1日 星期六
D21 令人感到麻煩的設計(6)──解決循環使用的現象
在設計的經驗裡,總是會“不小心”遇到像左圖那樣循環使用的現象,這是需要極力避免的。因為當B改變的時候,要追溯到使用它的A,如果A因為B的改變也作了改變,那麼使用A的B又要再度改變而形成無窮迴圈。最簡單的解法,就是把A拆成兩個Package,使用B的放在A1,被B使用的集中在A2,這樣就可以讓循環使用的現象變為圖中的單向使用。可是實際遇到的情況大多都因為Package的內聚力過高而無法乾淨地分為兩個Package。
嘗試了幾種有問題的方式後,我提出右圖的解法:在B裡定義一個A2功能的介面(注意:這個介面屬於B),A維持原來使用B的動作,但內部原本要分割成A2的部分使之實作B提供的介面,如此一來A使用B與A實作B都可以是由上往下的使用關係。
2007年8月31日 星期五
D20 令人感到麻煩的設計(5)──Abstract Class的設計
同樣的程式碼抽取到方法讓大家共用,同樣的方法抽取到共同的父類別來使用,這是設計中對於程式碼的reuse。
這是是一個簡單的繼承架構,ClassInterface下有一個DefaultClass作為基本實作類別,另外衍生了三個子類別。理論上DefaultClass應該會做出介面裡定義的全部動作,子類別在方法裡有不同的動作時再override(覆寫)父類別的的方法。
有的時候介面的方法很明顯地是該由各個子類別來決定如何實作,這個時候父類別就不可能有可以共用的方法實作。例如一個交通工具介面裡有move()方法,因為每種交通工具移動的方法不同,所以實作介面的基本類別,move()方法就應該保持為abstract等待子類別來實作。
會有人說:先隨便實作一個方法再由子類別override不是也可以嗎?設計時不該讓物件進行沒有必要的動作,不僅會令物件進行沒有定義的行為,同時不必要的override也讓程式更難追蹤。Abstract Class是無法產生實體的對應,只是對於物件的定義而不會變成物件;在意義上就像生物學裡的分類,界門綱目科屬種各定義了不同所屬物種的特徵,到最後的分類時(種)才有所有符合所有特徵定義的生物。
交通工具類別已經宣告move()為abstract,但是直接把汽車、輪船、飛機等直接繼承交通工具也會有問題,因為子類別裡還是會有多個類別有同樣動作的情形,一有改變就可能牽動到所有子類別。理想的設計是再衍成對應的分類類別,子類別再去繼承分類類別。一組類別要設計成這樣時比起快速地直接使用一個類別,當然會多花很多的時間,不過應做的工作還是該去做好的。
這是是一個簡單的繼承架構,ClassInterface下有一個DefaultClass作為基本實作類別,另外衍生了三個子類別。理論上DefaultClass應該會做出介面裡定義的全部動作,子類別在方法裡有不同的動作時再override(覆寫)父類別的的方法。有的時候介面的方法很明顯地是該由各個子類別來決定如何實作,這個時候父類別就不可能有可以共用的方法實作。例如一個交通工具介面裡有move()方法,因為每種交通工具移動的方法不同,所以實作介面的基本類別,move()方法就應該保持為abstract等待子類別來實作。
會有人說:先隨便實作一個方法再由子類別override不是也可以嗎?設計時不該讓物件進行沒有必要的動作,不僅會令物件進行沒有定義的行為,同時不必要的override也讓程式更難追蹤。Abstract Class是無法產生實體的對應,只是對於物件的定義而不會變成物件;在意義上就像生物學裡的分類,界門綱目科屬種各定義了不同所屬物種的特徵,到最後的分類時(種)才有所有符合所有特徵定義的生物。
交通工具類別已經宣告move()為abstract,但是直接把汽車、輪船、飛機等直接繼承交通工具也會有問題,因為子類別裡還是會有多個類別有同樣動作的情形,一有改變就可能牽動到所有子類別。理想的設計是再衍成對應的分類類別,子類別再去繼承分類類別。一組類別要設計成這樣時比起快速地直接使用一個類別,當然會多花很多的時間,不過應做的工作還是該去做好的。
2007年8月30日 星期四
D19 令人感到麻煩的設計(4)──物件關係的影響
在B21裡提到了物件的三個關聯:is、has、use,其中關係最緊密的是is,其次是has、最鬆散的是use。
左圖的專案物件目前繼承CompClass2,在宣告的同時專案物件就永遠是一個CompClass2而不可能會變成其他種類,動作的時候就只能依CompClass2的規範。右圖的專案物件使用一個變數來記住內含的CompClassInterface,可以依需要操作設定的CompClassInterface實作(注意:三種CompClass都可以設定),這就增加了專案物件使用的彈性(Strategy Design Pattern)。
右圖的專案物件本身可以使用前一篇的方法繼承另一個Class,加上混用Wrapper Design Pattern讓專案物件同時實作CompClassInterface,並將實作方法全部直接呼叫strategy設定的CompClass的話,我們就可以擁有身具兩種CompClass的專案元件應用在更多的地方。(必要時還可以增加其他的strategy來擴充功能)
use是在呼叫時傳入物件,物件只在方法中使用而不另外記憶下來;在服務性質的API可以大量使用這種方式。呼叫Web Service時使用的就是這種物件關係。
左圖的專案物件目前繼承CompClass2,在宣告的同時專案物件就永遠是一個CompClass2而不可能會變成其他種類,動作的時候就只能依CompClass2的規範。右圖的專案物件使用一個變數來記住內含的CompClassInterface,可以依需要操作設定的CompClassInterface實作(注意:三種CompClass都可以設定),這就增加了專案物件使用的彈性(Strategy Design Pattern)。
右圖的專案物件本身可以使用前一篇的方法繼承另一個Class,加上混用Wrapper Design Pattern讓專案物件同時實作CompClassInterface,並將實作方法全部直接呼叫strategy設定的CompClass的話,我們就可以擁有身具兩種CompClass的專案元件應用在更多的地方。(必要時還可以增加其他的strategy來擴充功能)
use是在呼叫時傳入物件,物件只在方法中使用而不另外記憶下來;在服務性質的API可以大量使用這種方式。呼叫Web Service時使用的就是這種物件關係。
2007年8月29日 星期三
D18 令人感到麻煩的設計(3)──吸收改變的避震器

上面這張圖的狀況是專案物件會廣泛地使用一個底層API。一開始我們很直覺地使用左圖裡的方式直接呼叫BasicAPI的方法,而且沒有遇到什麼問題;後來我們發現有些連續呼叫數個BasicAPI的方法在專案裡很常發生,需要抽出另外成為固定可用的API,於是我們建立了一個BasicAPIExt1,在遇到需要呼叫特定連續方法時使用新的BasicAPIExt1,其他情況則直接呼叫BasicAPI。
中間的圖我們可以看出,專案物件有些呼叫BasicAPI,有些則呼叫BasicAPIExt1,不僅層次的使用比較雜亂,每個專案物件有使用關聯的物件也變多了。設計該是具有層次與抽出共用部分簡化關係的,所以會用右圖的設計,讓BasicAPIExt1繼承BasicAPI來達到這些目的;而且未來BasicAPI的某些改變只會影響到BasicAPIExt1而不會影響到專案物件。
這個範例應用的範圍不只是API,應該包括專案所有使用(static method)或繼承關係的設計範圍。如果能夠費心把專案與元件之間的使用關係都加上一個類似避震器用途的類別(即使只是沿用也要加)的話,將會更輕易應付突然發生的改變。
2007年8月26日 星期日
D15 令人感到麻煩的設計(2)──所有水平層次的設計
在設計每一個層次時,我們同樣也可能會遇到其他人送來的設計圖長得類似下圖。他想表達的是三個不同的元件各有其定義的介面與實作的入口類別。


不管我們怎麼決定設計,其實該有的東西都有的話系統應該都能正常做出來。但設計的目的除了讓每個物件各司其職之外,另一個重要的目的是要去蕪存菁,把重覆的程式碼集中到上一層的類別裡。上面的設計因為實作直接對應介面而沒有任何父類別,因而造成每個實作類別都得存放所有的程式碼在裡頭;那麼上一篇提到的介面基本方法就會到處都是。
理想的設計,還是必須多花一點功夫設計一個BasicCompInterface與BasicCompImpl來收集通用的屬性與方法,實際使用的元件繼承他們之後再撰寫自己的介面與實作。如果元件衍生時有因專案類型而適用在不同行業時,最好針對每一種專有的行業再設計一組對應的介面與實作,如此才能把不同的屬性與方法放置在他們應該存在的層級,而且可以快速篩選出適用特定行業的元件。
理想的設計,還是必須多花一點功夫設計一個BasicCompInterface與BasicCompImpl來收集通用的屬性與方法,實際使用的元件繼承他們之後再撰寫自己的介面與實作。如果元件衍生時有因專案類型而適用在不同行業時,最好針對每一種專有的行業再設計一組對應的介面與實作,如此才能把不同的屬性與方法放置在他們應該存在的層級,而且可以快速篩選出適用特定行業的元件。

2007年8月24日 星期五
D13 令人感到麻煩的設計(1)──Data Model的設計
直覺地把系統的動作寫成程式是最快的開發方式,在執行上也很可能比較快。設計雖然也同樣地把同樣的系統動作寫為程式,但是多了很多與處理層次有關的類別,不僅在開發上相對緩慢,執行上也會因為呼叫的路徑變長而變慢些。

想像一下,我們是印表機延伸工具的開發廠商。依前面所談的,公司已經準備好Base Model作為基本資料類別,現在第一版系統要支援三款印表機。在一開始的Data Model設計有人提出了像上圖的方案,身為Leader的你應該通過這個Data Model設計嗎?
這個設計基本上當然可以讓系統正常執行(就算只用Basic Model也可以,但那會一團亂),然而設計的目標是讓系統的程式分門別類地形成層次。這裡的問題在於印表機延伸工具的系統,會定義出一些自己專用的資料與存取方法,以這個設計來看,那些專用的方法都必須實作在三組Data Model裡,不管是個別使用的或者是三種用法都相同的。
如果我們曉得要把重覆使用的程式區段抽取為方法來使用是正確的方式,那麼把重覆使用的方法再抽取到一個類別來reuse也是應該做的事情。雖然設計因此多加了一個層次,但是每個物件各司其職不作重覆的事,是OOAD所要表達的一個重要精神。

想像一下,我們是印表機延伸工具的開發廠商。依前面所談的,公司已經準備好Base Model作為基本資料類別,現在第一版系統要支援三款印表機。在一開始的Data Model設計有人提出了像上圖的方案,身為Leader的你應該通過這個Data Model設計嗎?
這個設計基本上當然可以讓系統正常執行(就算只用Basic Model也可以,但那會一團亂),然而設計的目標是讓系統的程式分門別類地形成層次。這裡的問題在於印表機延伸工具的系統,會定義出一些自己專用的資料與存取方法,以這個設計來看,那些專用的方法都必須實作在三組Data Model裡,不管是個別使用的或者是三種用法都相同的。
如果我們曉得要把重覆使用的程式區段抽取為方法來使用是正確的方式,那麼把重覆使用的方法再抽取到一個類別來reuse也是應該做的事情。雖然設計因此多加了一個層次,但是每個物件各司其職不作重覆的事,是OOAD所要表達的一個重要精神。

訂閱:
文章 (Atom)