15 四月, 2006 18:43
案例情境
世界大軟體公司有一款行銷分析的系統,具備了多功能的分析能力,並且能夠產出各種漂亮的分析圖表。當然,可以想像的到,這個行銷分析的圖表使用了第三方的廠商霹靂軟體公司設計的圖表產生元件。
This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.
15 四月, 2006 18:43
案例情境
世界大軟體公司有一款行銷分析的系統,具備了多功能的分析能力,並且能夠產出各種漂亮的分析圖表。當然,可以想像的到,這個行銷分析的圖表使用了第三方的廠商霹靂軟體公司設計的圖表產生元件。
23 三月, 2006 20:20
目前霹靂車公司有兩個生產工廠,分別是北工廠以及南工廠,各有一條組裝線,他們希望我們能夠設計出一個框架來,讓兩間工廠都使用遵循這個框架下設計出來的控制程式。此外,生產線會隨著產品設計部門的計畫調整生產的產品,因此要能夠彈性調整生產線的控制程式,以應付不同的產品需求。
經過幾天的訪談之後,我們整理出了下面的使用案例。
主要參與者:生產線控制人員 關係人利益: 資訊部門: 生產線工程師: 倉儲單位: 事先條件: 事後條件: |
Basic Flow: 1. 控制人員啟動組裝程式。 |
Alternate Flow: 2-5a.生產線傳輸帶發生故障。 |
特殊需求: |
關於使用案例
Basic Flow
主要情節,也可以稱作Happy Path,也就是一條能夠貫穿整個使用案例的成功路徑,並描述每個步驟的細節,過程中不會發生任何意外與分支,最終順利達成該使用案例的目的。
Alternate Flow
擴充情節,依據Basic Flow裡的每個步驟中會遭遇到的例外狀況來撰寫,如果再次遭遇到分支時,則以巢狀方式延伸下去。
| 撰寫使用案例的情節時,盡量避免描述到介面的操作,例如,輸入商品代碼要比用條碼機輸入商品代碼好。這是由於需求的分析重點在於釐清目的 (做什麼),而非實做的解決方案 (如何做)。 |
基本上本篇文章主要的目的還是為了示範Design Patterns如何應用在實際的案例上,然而現實生活中專案執行前原本就會有需求確認的工作需要先行,為的是將系統的範圍盡量先確認出來,需求的確認是軟體專案成功的基礎,畢竟軟體物件並不是天上掉下來的禮物,總是其來有自的。此外,也可藉由需求訪談的過程中明白系統(需求)的目的與願景何在。理論上,與專案目標相抵觸的功能或者需求是盡量要避免的。
此外,使用案例的寫法並沒有所謂的標準,但是基本上符合其主要的精神,就是要藉由使用案例的探討釐清需求的內容與目標,在這其中,步驟的描述文字是最重要的一環。至於UML中的使用案例圖形基本上可以幫助我們釐清系統的大範圍以及藉由圖形的展示來加速理解系統的需求方向。但那並不一定是使用案例必須的組成要素。
領域分析
由上面的使用案例中我們可以得知,組裝線控制程式會是這個系統的主軸,當然也可能會牽涉到操控者的權限,授權系統等。此外,組裝的過程中會跟若干外部系統溝通,例如料材的控制系統,產品的庫存系統等。不過為了避免耗費太多篇幅在描述這些關係,我們關注的焦點主要還是會落在組裝線控制程式上。
![]() |
| 點選上圖可以檢視原始大小 |
由上面的領域圖可以知道,主要會有一個工廠的物件BikePlant以及組裝線物件BikeAssemblyLine。請注意,在這裡我們假設一個組裝工廠只會有一條組裝線,這與合理與否無關,純粹是根據現實需求中的條件來決定的,在本案例中霹靂車公司裡的一個工廠只會有一個組裝線(當然這可能也與我們偷懶有關係)。
| 領域分析裡的物件通常是直接引伸自現實世界中的抽象物件,所以請不要在領域物件中放入軟體物件的思維,例如資料庫物件之類的。 |
系統設計
在進一步的思考後,領域圖形會變換成軟體設計圖形,我們可能會決定由BikePlant擔任控制者(Controller)的角色,他會負責操控組裝線物件的動作,包括放置骨架、裝上輪子、裝上椅子…等。請注意,操控這些動作並不一定是由BikePlant來負責,只是因為在本案例中,BikePlant本身並沒有其他特殊的工作,因此讓它擔任控制者不會讓造成系統太差的內聚力。延伸來討論的話,如果BikePlant還有其他的工作,或許在BikePlant與BikeAssemblyLine之間還會多一個AssemblyController物件也不一定。

點選上圖可以檢視原始大小
上面的圖形是初步的一個成果,BikeAssemblyLine類別負責進行機車各個部位的組裝工作,而BikePlant則負責建立BikeAssemblyLine物件,以及去呼叫BikeAssemblyLine來進行組裝。
![]() |
| 點選上圖可以檢視原始大小 |
不過,相信大家都還記得我們的客戶霹靂車公司的一個需求。
| 為了日後生產線調整的需求,必須設計出一個一般化的框架,能夠讓霹靂車公司的資訊人員自行設計生產線的控制程式,但是不能因此更動到控制端的組裝流程。控制程式只能微調每個組裝步驟的內容,例如安裝把手時,高度或者料材的不同選擇。 |
也就是說,在實際操控組裝線物件的BikePlant中需要定義好組裝的流程,然後從此不再更動(這樣講好像太過嚴重,有朝一日霹靂車公司還是有可能改變組裝流程的。),而資訊人員只能在這既定的流程框架下設計組裝線的物件,而不能更動到BikePlant中的控制流程。因次,我們把上圖稍微改造了一下。

點選上圖可以檢視原始大小
這張圖看起來實在很難理解,沒關係,我們來解釋一下。
我們把BikePlant變成了一個抽象類別,然後在BikePlant中去定義組裝線物件操控的流程,最後讓子類別去繼承它。

點選上圖可以檢視原始大小
如同上面程式所描述的,StartFabricate函式內定義了組裝的流程,而且繼承BikePlant的子類別將無法覆寫它,就因此達成了子類別必須依循父類別所制訂的框架的需求。
此外,BikePlant中還有一個抽象方法GetAssemblyLine,該方法的作用是選擇真正負責組裝的組裝線物件,然後傳回給StartFabricate函示使用,這樣的作法將易於變動的部分(更換組裝線設定)抽離,讓系統不會因為更換組裝線而影響到了既定的程式。另外,將GetAssemblyLine抽象化的目的是由於,客戶的兩個工廠分別負責不同的車型組裝,要選擇的組裝線也不相同,因此我們無法在父類別中決定要使用那個組裝線。此外,這樣也可以在日後霹靂車公司擴廠時,直接增加一個繼承BikePlant的子類別即可。
接下來,我們繼續看BikeAssemblyLine類別。
同樣的,我們也將BikeAssemblyLine轉換為一個抽象類別,在BikeAssemblyLine中我們定義了組裝線應該要負責的工作,並且強制子類別一定都要實作他們。

點選上圖可以檢視原始大小
我們來看北工廠要如何實作BikePlant類別。
![]() |
| 點選上圖可以檢視原始大小 |
再來是南工廠的實作部分。
![]() |
| 點選上圖可以檢視原始大小 |
由上面的兩個實作我們就可以看出來,子類別只需要實作選擇生產線物件的方法即可,事實上,就算子類別想要覆寫控制流程的方法也沒辦法。
當然,最重要的還是那些個生產線物件了,我們來看速克達一號的生產線程式該怎麼設計吧。

點選上圖可以檢視原始大小
當然,我們的目的是為了展示真的依照流程來執行這些組裝的動作,真正的工廠裡就是要去控制機器手臂來實際進行組裝的工作囉。
完成了所有程式之後,控制人員只要在面版上選擇北工廠或者南工廠要組裝哪一款的車子,就可以進行組裝了。

點選上圖可以檢視原始大小
由面版的結果顯示可以看出,速克達一號的確是按照抽象類別BikePlant中所設定的流程來組裝車子的。
延伸思考 各位朋友如果要把這樣的程式應用在實際工廠裡時,最有可能面臨的問題是什麼呢?當然,就是遠端控制了,我們實際上還需要設計一個能夠操控遠端元件的解決方案才能夠讓這個系統正式上線喔。至於要怎麼去設計這個架構,各位不妨腦力激盪一下! |
結論
這樣的架構其實在Design Patterns中有個名堂,就叫做Factory Method Pattern,工廠方法樣式,也就是本案例中負責產生生產線物件的GetAssemblyLine方法。它的角色其實就是工廠方法。
順帶一提的,在父類別制訂流程框架的部分,我們也有個名堂可以稱呼,就叫做Template Pattern,模版樣式。也就是在BikePlant抽象類別中StartFabricate方法所負責的工作
16 三月, 2006 09:39
筆者在近日因某些關係與一位已退休的東海大學物理系劉教授聚餐聊天,在談話的過程中我似乎與教授生活在不同的世界一般,可想而知物理是一切科學之本,而教授的思路與談吐都環繞著他們數十年來所追求的真理與中心思想,我想這便是一種思考原則吧!
相對的,若我們將焦點縮小到物件導向設計,它的中心思想筆者由自己的經驗與書籍上可歸納出就是『將真實世界的物體抽象化』,不過這種說法我想對於一般的讀者來說會充滿了疑惑且難以想像,就好像我與劉教授一樣,無法進入他的思考境界一般,因為充滿哲理的說法,是需要很多知識與智慧的結合才能夠體會的。
這篇文章的目的就是為了協助初學物件導向設計的讀者用最簡單的方式將真實世界的需求轉換為類別設計,以筆者的經驗與心得來引導讀者去分析及找出學習的盲點。
筆者在學習物件導向程式設計(C++)過程中,翻過了很多的書籍,跳過每本書的前一至三章說明程式設計的基本功夫不說,大部分書上的內容結構都離不開物件導向程式設計的通則『繼承』、『封裝』及『多形』等,也會給予很多的範例讓讀者練習,不過,當讀者要接觸真實的專案設計時,不一定能立刻將問題及需求轉換為物件導向的設計方法。
其實這是很正常的,因為書上都會盡量以範例來闡述物件導向,缺少了系統分析與設計的部分,因此,大部分的初學者會碰到這樣的瓶頸,筆者也不例外,不過當時並沒有前輩們的指導,協助我如何去思考及實作,所以,只能從實務中去體會學習如何以物件還思考問題了。
為什麼會碰到這個瓶頸呢?我想在於書本上的範例過於強調以一個案例敘述來解釋物件導向程式設計(OOP)的概念,忽略了引述案例所應包含的前因果,讓讀者可以『初步』的去體會或瞭解一些物件導向設計(OOD)的概念,如此極有可能造成剛學會OOP的人,在轉換到OOD時有學習的斷層存在,甚至與資深的程式設計師或分析師討論問題時,也有可能發生溝通上的困難,這個論點我在『物件導向OO技術基礎講座』一書中作者也同樣的點出此問題。
初學物件導向程式設計的人員,不必過於將真實世界的所有事情都要以OO的觀點來看待他,因為此時你所學習到的只是如何去實踐OO系統開發的工具而已,你可能會一直停留在物件導向的reuse或extend等執著當中,所以你會遇到為什麼使用OO會這麼的礙手礙腳的問題,此時別灰心,因為只是你缺少了一些思考的觀點、原則及方法等經驗吧!
需求與類別的關係
在系統開發時的工作要點之一就是系統分析,所謂分析是去找出問題及需求,在物件導向分析中我們經常將分析區分為:
在需求分析階段,此時是專案開發時最重要也最複雜的階段,是必需經過很綿密的思考及溝通才能產生較為完整的需求,這也必須依賴豐富的經驗與專業素養,甚至良好的公關能力才能順利的完成,所以,當你還是初學者的時候,若身旁有資深的人員帶領那是最好也不過了,若沒有當然還是必須先稍微做一下功課後才去執行此項工作,一方面可以在最短時間內取得客戶對你的信任,另一方面也容易打開彼此之間的關係。關於這方面的問題,我想不需要論述得太多,而筆者這篇文章主要還是把重點放在需求與物件的主題中,所以不再多加闡述。
一般需求分析時,都會先列出系統的參與者(角色),及針對這些參與者的問題需求寫成描述,這階段對於瀑布式需求分析(Waterfall)或物件導向需求分析等方法來說其實大同小異,就如同『USE CASE』使用案例來說,其實它主要是利用「文字敘述」來描述需求的內容,之後訂定的UML才將Use Case Diagram作為輔助表達參與者與系統之間關係的一種圖形,但在諸多文獻中仍可明確得知,Use Cases的重心仍然在文字敘述,單就這點而言,需求分析的工作並不會因為物件導向的開發而有所不同。
總結來說,在系統分析方法上的主要差異性在還是於『物件分析』階段的思考模式。
瀑布式需求分析階段 | 物件導向需求分析階段 | |
需求描述 | 文字描述需求 | 文字描述需求(USE CASE) |
圖形工具 | 傳統流程圖 | Use Case Diagram System Sequence Diagram |
其他 | 需求轉化為物件之分析 |
在物件分析階段,主要就是在需求描述的問題的範圍內找出類別來,有時會稱作「問題領域物件」,物件經一般化後便產生類別來,此時你就已經有了系統分析的初步結果了。然而問題往往是初學者對於如何需求敘述中找出類別、方法及屬性也會感到力不從心,因為這才是將系統需求轉化為物件導向系統分析的關鍵所在,不過資深的前輩們也都知道這樣的轉化過程,其實是需要很多的經驗所累積的,而這對初學者而言卻是最難跨出的一步。
因此在本篇文章中,我們先將以簡單的方法來介紹並結合筆者們常用的思考方式來幫助初學者,以期能夠縮短學習的曲線,至於更詳細的內容我們將在往後文章中來作闡述。
筆者們常用的方法中,就如『Applying UML And Patterns』一書中所提的需求分析與物件導向分析與設計的方法中一般,大約可分為四個步驟:
不過上述的步驟方法大都是已經有該專案的Know how且有OOAD的經驗者之思考及實作的方法,因為他們已經可以憑著經驗,而能夠很直覺得定義出需求中所需的物件、屬性及方法,更進一步的能夠分析出物件與物件之間的關係,不過,筆者整理出下列的方法,可以協助初學者較容易找出上述幾個步驟中可以產出的一些分析文件及圖形:
a、 由需求中找出類別
從每個需求敘述中找出『名詞』來,每個名詞都是類別的候選者,再篩選出該類別可能含有行為及屬性,若該名詞不具備行為(方法)及屬性,則可能是某類別的屬性之一,此方法可對應著上述的『定義領域模型』的步驟。
b、 找出類別的方法
找出需求敘述中的動詞(is、was、have),這將會是方法的候選者,且透過需求敘述你可以再將這些動詞與上一步驟所找出的名詞結合,繪製出所謂的『互動圖』及『類別圖』。
c、 找出類別的屬性
類別候選者所消除的名詞可能就是某些類別的屬性,這也是『定義領域模型』的一種另類思考的方法。
d、 驗證你的需求
重新驗證需求中的每個敘述,並檢討可能遺漏的必要功能。
結論
關於需求轉為物件導向系統分析的方法諸多繁瑣,而且真的需要經驗的累積,筆者所闡述的是盡量讓初學者得到一些啟發,至於對錯可能都會因人而異,且這個主題充滿了很多的哲理,要描述這些哲理不是一篇短文能夠涵蓋的,因此筆者先以這篇文章作個開場白,因為或多或少在未來的文章裡,大都會提到與本文相關的一些議題,因為這篇文章是物件導向設計與分析的核心,所以,一些關鍵的議題將會在未來為各位說明,敬請期待囉~
12 三月, 2006 21:55
在物件導向的設計的實務上,最困難的工作之一就是「如何決定物件的責任以及物件之間的關係」。
我們可以由兩個不同的觀點來解釋這句話:從抽象的觀點來看,物件的責任分配應該易於理解與合理,不讓人困惑,並且讓物件之間的運作盡量不會互相干擾;從實做的觀點來看,讓物件之間相依性降到最低,不會因為特定物件的變更而引起了整個架構的漣漪,確保設計擴充上的彈性。
現代社會由於工作繁忙,所以許多上班族都成了外食族,但是在筆者還小的時候,全家人都會回家吃晚飯。到了晚上,爸爸的責任是在家翹著二郎腿看報紙。哥哥跟我的責任則是好好讀書,長大以後要成大事立大業,光宗耀祖,讓媽媽有面子。媽媽的責任是負責煮菜,晚上全家人吃晚餐時有一頓美味的餐點可以大快朵頤。每個人都有自己要負責的工作,彼此分工合作支撐起這個美滿家庭。
如果爸爸沒有報紙看的話,爸爸一個人會覺得無聊,但是對我們沒有影響。哥哥如果期中考沒考好的話,媽媽會覺得沒面子,但是我跟爸爸覺得沒關係,反正勝敗乃兵家常事。
但是晚上的時候每個人都要「吃飯」,而飯菜是靠媽媽「煮菜」得到的,所以每個人如果要吃飽,要依靠媽媽煮菜這件工作能夠順利完成,有一天媽媽生病了,結果全家人都餓到雙腿發軟,我們全家才體認到一個事實,媽媽能不能好好煮菜這件事情會影響到全家人,所以我們必須不惜一切讓媽媽可以每晚好好的「煮菜」。
要活下去就要吃飯是一件不可違逆的事情,就好像在軟體系統內,希望物件之間完全沒有關連是不可能的,所以當我們發現大部分的物件都需要相依於某幾項特定物件的工作時,我們唯一能做的,就是盡全力讓這幾個重要物件的介面能夠保持穩定,而實做的方式就是善用介面讓物件的相依性與實體類別脫勾。
回到我美滿的家庭,既然一定要人煮飯,如果媽媽生病了沒辦法煮飯時,一定要有人能夠替代媽媽把飯菜煮好,可能是請庸人煮(可是請不起庸人),也可以叫外賣讓餐廳的廚師來煮。
從實做的觀點來看,我們讓煮飯這件事情成為一個抽象介面,在正常的狀況下,我們讓媽媽負責實作它,但是一旦媽媽不能煮飯了,外面餐廳的廚師也可以實作煮飯的介面來幫我們準備好飯菜。這樣一來我們全家人就能從此過著幸福快樂美滿的日子了。
當然也可以讓我們全家人都學習如何煮菜,但是這樣一來大家都會搞的很累,而且平常白天要上班跟上課,根本沒辦法來得及晚上趕回家煮菜,媽媽也說不定會開始耍賴叫我們負責煮菜,自己卻跑去SHOPPING。這樣就違背了中國五千年文化說分工合作團結力量大的道理。
撇開物件導向設計的高深理論,上面所談論的觀念其實是最基本的馬步功夫,盡量讓物件之間的工作能夠彼此脫勾,不會互相影響,這就叫做降低物件之間的耦合力。每個物件之間有各自負責的工作,各司其職,彼此合作下達成整體的目標,這樣就叫做提高物件的內聚力。
物件導向設計時不見得一定要應用什麼複雜的Patterns,但是卻一定要堅守物件導向設計這兩個基本原則,事實上,任何Patterns的出發點也都是為了能夠符合這兩個基本原則,讓設計盡善盡美。
01 三月, 2006 11:14
哈囉,目前本部落格的文章主要可分為基礎理論與進階應用,您可循下列方式進行閱讀,以便獲得較佳的閱讀經驗,若有任何建議請隨時不吝指教,謝謝。
若是對於物件導向設計尚處於基礎學習的階段,建議先依照以下順序閱讀。
以下則是我們對於實務上系統分析與設計的一些建議,可以依照以下順序閱讀。
若是您對進階的Patterns議題討論有興趣,可以依照以下順序閱讀。
Design Patterns 第二炮 : Factory Pattern , Strategy Pattern
Design Patterns 第三炮 : 實作Strategy Pattern,讓元件像USB一般隨插即用!
Design Patterns 第四炮 : 由機車組裝生產線案例實作Factory Method Pattern (工廠方法樣式)!
Design Patterns 第五炮 :偷天換日,轉接器樣式(Adapter Pattern)
最後,關於物件導向設計的議題,我們亦發表了若干討論的文章。
22 二月, 2006 09:00
會想要談論這個題目其實是有緣故的。
自從.Net Go2 OO這個部落格成立以來,我們發表了幾篇關於物件導向基礎的文章,關心的眼光都停留在介面這個重量級的角色上,其中包含了介面使用的時機以及為何要使用介面的正確觀念;另一方面我們也發表了幾篇較為進階的文章,初步的介紹了Design Patterns中較為簡單的幾個樣式,其中也包含了一篇如何應用Pattern來實做的案例。
但是我們逐漸聽到了類似下面的問題:「究竟為什麼要使用物件導向設計,在現實面究竟有什麼好處存在?在現實上這麼辛苦麻煩的設計架構究竟有沒有價值?」
我不禁回想起這些年來學習程式設計以及物件導向設計的過程,這中間我不斷的聽到這樣的標準答案,所謂物件導向就是封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism)。我也在許多書本上學習了這些詞彙的解釋,也懂得了如何利用這些特性來撰寫程式(OOP),但是即便如此,我還是沒辦法很輕易的回答上面的問題,我也很少聽到或者看到一個可以讓我由衷讚嘆「對啦,就是這樣!」的說法。
接著我開始接觸物件導向設計(OOD),解釋物件導向的說法開始變的更加虛無飄渺了,每個人都用「汽車與卡車都是車子」的故事來為我解釋物件導向。可是就算我知道汽車跟卡車其實有親戚關係,我還是沒辦法自圓其說為什麼我要使用物件導向設計。難道就因為大家都說它好所以它就好嗎?
然而隨著接觸的層面越來越廣,對於物件導向設計我逐漸有了另外一種看法。
我們時常提及的封裝、繼承、多型或者物件的分析本質都是物件導向設計的特色,又或者說物件導向設計是由這些特質所組合而成的。但是這並不足以構成物件導向成為主流的原因。
一個男人的如果長的很帥可以是他的特色,但是如果長的帥沒辦法為他帶來好處,那我們就不能說長的帥是他的優點。
那麼封裝、繼承、多型這些特色究竟可以為我們帶來什麼好處?
換個角度
首先,封裝讓程式碼易於理解。我們可以從一個類別的名稱以及他的行為去理解與記憶它和系統之間的關係與責任,這樣的特性可以縮短日後我們重新檢視程式碼時所要花費的時間,沒有人會說這不是好處吧?
再來,繼承讓程式碼可以重複使用。就拿剛剛提到的車子、汽車與卡車的例子來說吧。我們把大部分重複的程式碼寫在車子的類別裡,以後的十種汽車跟八種卡車都可以直接享用車子中已經定義好的程式碼與功能。我們因此縮短了浪費在重複性工作上的時間。
最後,多型讓程式易於擴充與維護。針對共通的介面來撰寫程式時,可以使得物件之間的關係被鬆綁,讓軟體在日後進行擴充或者個別物件修改時不需要更動到既有的程式碼。當軟體龐大到一定程度之後,擴充與修改對程式設計師而言往往是非常痛苦的一件事情,我對於善用介面可以達到的效果非常推崇,可以說相當程度的提供了軟體品質的穩定。
撇開所有美輪美奐的說詞,回來面對現實,我們就是為了能夠讓程式碼重複利用,讓系統易於擴充與維護所以才要使用物件導向。這一切最直接的效果就反應在縮短軟體開發的時間以及增進軟體的品質穩定度上,讓我們能夠專注在真正重要的工作上面。美麗或者基本教義派的崇拜都不是物件導向之所以存在的原因。
站在實作的角度來看,物件導向的存在就是為了增進程式設計師工作的效率以及軟體的品質,以這樣的觀點而言,使用物件導向設計是再實際也不過了,千萬別說使用物件導向是天真而不肯面對現實。
水能載舟亦能覆舟
不過問題來了,既然物件導向這麼優秀,為什麼還會有這麼多人不願意把她娶進門,仍然嫌東嫌西的呢?
其實上帝是公平的,這世界上本來就不存在十全十美的事物,物件導向既然擁有這麼抽象而複雜的特性,也就造成了它在撰寫上一開始就必須克服的時間成本。為了能夠做到封裝,所以我們必須多花一些時間在分析物件的責任歸屬,為了做到繼承,所以我們必須多花一些時間在分析物件之間共有的責任與資料,為了日後擴充方便,我們必須多花一些時間創造出合適的抽象介面來讓物件實做。
這些都是伴隨著物件導向設計而來的工作。我們不能否認,使用結構式的程式設計不必付出這些時間成本;當然,自然也享受不到繼承與介面的優點。
持平而論,物件導向的出現是為了讓程式設計師能夠用更短的時間開發出更優良的軟體,如果在某些現實條件下的評估結果是,不使用複雜的物件導向設計勝於使用的話,又為何不選擇簡單一點架構呢?明明不會有擴充或者變動的可能性,硬要放個抽象介面在那邊礙眼,這是最不智的行為。
吃路邊攤的時候穿晚禮服,就算你長的再美,也只會讓人家覺得你有毛病而已,不是嗎?
結論
在現實生活中開發任何軟體專案時,我們通常希望在專案的進度、品質以及成本之間取得平衡,極端的要求在最短的時間內花掉最少的成本來取得最高的品質是不可能實現的夢想。
物件導向設計是軟體開發歷史演進下的產物,集結了許多前輩的智慧與經驗而成,即便是如此,也不可能達到上述夢想中的境界。我們當然可以追求最完美的物件導向設計架構,但是必然將犧牲開發的進度以及成本。相對的,一昧追求時效捨棄良好的軟體設計也不可能得到合理的品質。
許多時候並非物件導向設計無用,只是在有條件的選擇下被捨棄罷了。
14 二月, 2006 16:08
之前筆者發表過兩篇關於Design Patterns議題的文章,用意是讓讀者能夠初步瞭解Strategy Pattern與Simple Factory Pattern的概念以及應用的方式,不過在現實世界中的應用情況往往還要更複雜些。因此,本篇文章中我們將進一步探討在MicroSoft.Net的平台上實際應用Strategy Pattern概念時會遭遇什麼樣的問題,以及應該如何解決。
在本篇文章最後,我們將會完成一個實務應用,讓演算法獨立為一顆元件(DLL),而要使用演算法的軟體系統只需要使用參數來設定組件名稱,就可以直接將演算法元件掛上系統,不需要重新編寫程式或者編譯,就彷彿是電腦的USB介面一般,隨插即用。
系統分析與設計
如同之前文章所解釋的,Strategy Patten的用途是當我們在設計系統,面對需要彈性抽換商業邏輯或者演算法議題時,用來將客戶端以及演算法的物件之間鬆綁的解決方式,用比較容易理解的說法,我們可以說Strategy就如同是一個Switch在運作著,使得系統可以在不同的演算法之間作彈性切換。
請看以下的表格,裡面有三個演算法需要做處理。
輸入值 |
演算法名稱 |
運算規則 |
A |
StrategyOne |
A * 5 + 10 |
A |
StrategyTwo |
A * 10 |
A |
StrategyThree |
A * 2 |
依照之前我們提過的原則,我們會將所有的演算法設計一個共同的介面,並且打算讓三個演算法都實做這個介面,讓客戶端的程式只能接觸到這個介面,來達到與客戶端之間的程式鬆綁的目的,如下圖。
像這樣的架構在客戶端可以在程式中依造情況切換至不同的演算法物件,但是明眼人其實看的出來這樣是有缺陷的。在我們可以想像的情境底下,客戶端的程式功能大概是抵定不太會再做變動的,會有變動機會的其實就是實際執行更換演算法的程式部份。此外,還有一個問題就是,客戶端在實做IStrategy物件時,還是難免會接觸到演算法物件,這樣其實就失去了我們使用介面的美意了,實體演算法物件的類別名稱還是寫死在程式裡了。

依照物件導向的隔離原則,我們會希望將容易變動與不會變動的部份隔離開來,以免因為修改或擴充時影響既有的程式而造成預期之外的狀況發生。因此我們可以再調整成下面的架構。
依照上圖的架構,我們會將切換演算法的工作放到StrategySimpleFactory物件裡,這麼一來客戶端就不會接觸到最後實際的演算法類別,也就是說我們將會變動的部分都抽離集中到StrategySimpleFactory了,如同下面的程式,客戶端只剩下兩行程式就解決了,而且永遠不需要知道究竟實做了哪個演算法類別。
IStrategy obj;
obj = (IStrategy)StrategySimpleFactory.GetStrategy();
還記得文章ㄧ開始提到的嗎?我們要完成的是可以掛載演算法元件的系統,怎麼現在都還將選擇演算法類別的程式寫在SimpleFactory裡面,所以我們還有一段路要走,請再集中一下精神吧。
系統實做
接下來我們要重新調整一下我們的架構,由於我們最後要掛載演算法元件,因此我們將把演算法實作的類別獨立在一個類別庫專案裡。然後將客戶端建立一個WindowForm專案。最後IStragety介面由於同時需要被實作的演算法以及客戶端參考到,所以將這個介面獨立出來成為一個類別庫專案讓其他兩個專案參考。

首先我們要先完成IStrategy介面的類別庫,如同下列程式碼。

接著,我們在演算法類別庫中實作兩個IStrategy的演算法類別,分別是StrategyOne以及StrategyTwo。


等等,不是說有三個演算法嗎,第三個呢?
請先稍安勿躁,往下看就會知道了。
在本文前面曾經提過,SimpleFactory被視作是一個Switch在運作,此時程式中實際上並沒有出現實際進行切換演算法的程式碼,而是被動態繫結的程式碼所取代。
這段程式的作用是這樣的,它會依照傳入的參數組成組件與類別名稱,然後以動態繫結的方式載入該顆元件並且建立該型別的執行個體。最後再將建立的執行個體給傳回客戶端程式。也就是說,原來用來切換的動作我們用一個外部參數傳入系統的方式來取代,直接依照傳入的演算法名稱來連結到不同的演算法類別,並進行實做。
從上面的程式我們會發現實際建立物件的函式有兩個,分別是test()以及test2。這兩者的差異性在於test1是用來建立其他組件中的類別的執行個體的,也就是本篇文章主題中所提到的,可以抽換的獨立元件(DLL檔案)。
那麼test2的作用是什麼呢?
該函式的作用是如果系統要掛載的演算法類別是存在於同一顆組建中時,可以以該函式內的方式建立。使用的時機是例如我們用彈性動態繫結的方式建立演算法類別,但是又不希望元件獨立出來,可能有安全性的考量時,便可以折衷使用這樣的方式,讓演算法可以獨立維護也亦於擴充。因此筆者將第三個演算法放置在這個專案中,將會隨著本專案編譯在同一顆組建內。
第三個演算法如下。

最後讓我們看一下客戶端的程式,很單純的幾行程式而已。
實際部署
如同下圖所示,被掛載的StrategyImplement.dll需放置在系統執行檔的同一路徑下(若是Web系統,則放置於bin目錄下),不需要被參考至該專案內,當系統執行時會以動態繫結的方式載入該顆元件,並建立執行個體。

由於爲了讓抽換與掛載的效果突顯出來,因此筆者選擇用參數傳入的方式來動態掛載型別,但是實際上在系統設計時我們可以選擇用設定檔的方式來設定要抽換的元件。也可以選擇設計一個後台的管理介面,將組件檔的資訊記錄在資料庫裡,使用設定的方式完成皆可,端看系統架構規劃的方向決定。
執行的畫面如下所示:
演算法一

按下Test執行得到運算結果

演算法二

按下Test執行得到運算結果

演算法三,該演算法是放置於這個Windows Form組件內

按下Test2執行得到運算結果

在本篇文章中我們嘗試了如何利用Strategy以及Simple Factory兩個Pattern來設計一個彈性高的系統,不過其實這樣的設計理念是處處可見的。下一篇文章中,我們將探討在MicroSoft.Net的Framework也應用了這樣的理念而設計出來的架構,它也讓我們能夠設計自己的元件掛載到Framework的運作流程中,很意外嗎?請拭目以待。
在你成長的時候,漸漸的你會走到物件導向設計OOD,此時你可能會過渡的為了使用設計而濫用設計,而忽略了把實作OOP當作是OOD的一種手段或方法來加以利用,而筆者這篇就是要說明當你會了介面的程式設計後,你如何走出程式設計而走向架構設計。
我們先來簡單的介紹並協助讀者瞭解一些介面(Interface)設計的由來?介面它是一組行為和使用契約(usage contract)或規範,舉個例子來說,生活中你最常接觸到的是電腦,而電腦有主機板,主機板上有很多插槽,拿PCI(Peripheral Component Interconnect)插槽來說他就是個介面,插在此介面上的通稱介面卡,不管你是購買PCI的網路卡、音效卡、顯示卡,基本上你可以看出所謂的『金手指』都長的一樣,因為他們必須符合PCI的標準規格來製造不同功能的介面卡才能與主機板溝通,PCI的標準規格就是定義此介面的共同規範,才不至於介面卡的製造商各說各話到時做出來的東西不能插到主機板上而傷腦筋,而且主機板英文是Mother Board或Main Board,在意義上他們是『整合』很多功能的物件,所以介面卡的製造商不可能要求主機板商去改變原來的設計,如此會造成不堪的後果發生,因此必須協議出共同的溝通方法及管道,這就是介面存在的意義了。

由這個例子,我們也不難想像在物件導向世界裡的介面是有多麼的重要了吧。
一個有經驗的程式設計師或系統分析師應該都會希望設計出來的架構具有彈性,可以因應需求隨時抽換或新增功能。在『Head First Design Pattern』介紹設計模式時,也特別強調為什麼要針對介面寫程式,他引用了鴨子的例子就好像上述的主機板一樣,若一開始主機版廠商制訂了一個不一定是PCI介面的插卡,其上有已經安裝有64MB記憶體的插卡,然後要求各種功能的插卡製造商依照這張來設計顯示卡、音效卡、網路卡,想想在這種情況下製造商該如何是好呢?假設原本只有顯示卡需要記憶體的,但是現在連音效卡、網路卡都要以這張插卡為模版才能製造出可供主機板讀取的插卡,那麼萬一這張插卡要拿到另一個主機板上安裝時,可能就不相容了。此外,原本音效卡製造商不需要負擔記憶體成本費用的,現在他也必須承擔,這樣反倒是失去制訂此插卡模版的意義了。
因此,聰明的插卡製造商集體要求主機板商必須改進此問題,此時主機板商應該退一步,只制訂插卡的介面即可,讓原本是音效卡、顯示卡等製造商,只要依照主機板商制訂的介面及插卡商各自定義什麼是音效卡、顯示卡上該有的元件即可,這樣顯示卡商也只要依照顯示卡工會定義的顯示卡元件,而且顯示卡上的各元件也可隨時抽換或再加上各種附加的功能即可製造出相容性極高的顯示介面卡,這樣不是皆大歡喜嗎?
我們在設計資訊系統也猶如設計主機板上的功能一樣,必須設法找出可以抽象且獨立出來的元件,並將他們盡量以介面的方式來設計你的系統,這樣就算客戶再怎麼要求你修改原有的功能都是輕而易舉,而不會因為要牽一髮而需動全身而難以應付不斷改變的需求,所以要針對介面來寫程式,如此,在功能元件介面不互相干擾的情況下,你只要針對你要修改的功能來新增修改刪除內容,也不需考慮會不會影響其他既有的功能了。
接下來有機會我們將介紹如何將上述的觀念應用在實做上。
30 元月, 2006 23:11
之前曾經在 Facade Pattern 一文中提到Data Access Layer實踐Façade Layer的觀念,這是對於客戶端呼叫時提供了穩固以及不易變異的介面。
不過這是對於客戶端,也就是外部使用這個物件時的觀點,那麼今天我想可以進一步探討這顆Data Access Layer的套件在內部怎麼樣設計會比較好些。
假設今天老闆要我們設計一個銷售系統,我們打算將銷售系統中的Data Access Layer抽離出來(抽離出來的理由應該不用再贅述了),我們需要考量下面的因素:
日後這個銷售系統可能搭配SQL Server或者Access甚至其他的儲存裝置來販售,所以我們必須將Data Access Layer的彈性與擴充性功能考量進去。
也就是說,我們在程式中使用到這個套件的時候,最好可以不知道我們現在是聯結到SQL還是Access的裝置上,那麼日後就只要變更Data Access的套件,高階的商業邏輯套件可以原封不動。
經過思考之後,我們可以利用下面這樣的方式來達到目的。



看起來這樣做是沒什麼問題的,我們只要依此類推,再增加要取得DataSet、DataReader、Scalar等回傳型態的函式,然後功能依照平台的不同導到各自負責的函式去處理就好了。但是,要是增加了ㄧ個平台呢?比如說是用Oracle連結的儲存裝置呢?
那我們只好回去打開這顆套件,然後加上負責處理Oracle的函式以及在負責分派的四個函式加上屬於Oracle的分,每增加一個平台選項時必須異動到的地方有八處,如果這顆元件功能再強化一些細節的功能的話,只會更複雜而以。
會不會有點糟糕?
再思考下ㄧ個問題,資料庫平台的數量不算太多,算來算去也就那幾種,所以用這種作法勉強可以接受,但是如果這樣的作法用在其他的議題上,萬一衍生的種類來個十項,這支程式大概要又臭又長又難維護了……
或許我們可以用下面的作法試試看。
我們設計了ㄧ個Icommand的介面,定義了執行資料庫的幾個函式與屬性,然後讓其他類別去實做它。我們可以選擇先實做負責處裡SQL平台的物件,因為這是我們的老闆要我們先出這個版本的產品。
在SOLCommand類別裡,我們才真正實做了幾個處理SQL資料庫工作的函式。接下來如果我們要實做處理Access的類別時,只要新增一個類別OleDBCommand然後實做Icommand介面就好。
上面的作法好處是什麼呢?
不管我要增加幾個衍生的平台,都不會有機會去碰觸到已經寫好的程式碼,我們都知道,當系統隨著時間增長,會藏下許多我們都不知道的地雷,所以能夠不去修改到舊有的程式碼是最理想的,上面的做法可以滿足這樣的需求。
接下來我們有另一個課題要解決,就是不讓客戶端知道最後實做的到底是那個平台的類別,對使用這顆物件的客戶端程式碼來說,它面對的永遠都只是Icommand介面。
我們讓CommandFactory物件來決定最後實做的類別究竟是哪一個,不管最後是要實做SQL還OLEDB,都讓CommandFactory來面對客戶端程式,然後用CreateCommand來回傳Icommand物件。
如同上面的範例,對客戶端來說,將永遠不會知道究竟是使用了哪種資料庫物件,它面對的只有一個類別跟一個介面,CommandFactory與Icommand,不論後面究竟已經衍生了幾百種類別。換句話說,我們可以在客戶端程式不知情的情況下擴充甚至更換掉商業邏輯的元件,是不是很棒呢?
這樣的做法特別適用於企業邏輯時常需要的更換或擴充的系統,那麼就可以在不變動到舊有程式下達到擴充的目的。
最後,這樣的模式可以算是Factory Pattern 加上 Strategy Pattern的混合應用。
在這個例子中,Factory指的自然就是負責建立符合實作介面的CommandFactory,而Strategy指的是利用客戶端只相依於一抽象介面的特性下,達到彈性替換演算法的功能。
19 元月, 2006 21:55
在討論什麼是Facade Pattern之前,我們先來看看一個在實務上我們經常會用到的技巧。
在常見的三層式架構中(3-tier)中,最底層通常會是Data Access Layer,這個Layer主要將與實體資料儲存裝置之間的溝通時需要用到的繁瑣步驟封裝起來,以方便Business Logic Layer在與資料儲存裝置溝通時能夠有一個簡單以及穩固的開放介面。


在上面的範例中我們可以看到,當客戶端希望執行資料庫查詢時,只要將組成的指令傳送給ExecuteCommand這個函式,其餘內部的Connection與Command物件的建立與實際的執行就交由DataAccessObject去代為處理,簡化了客戶端原本需要自行撰寫的程序。
這種將做法有許多好處。
首先,我們都了解資料儲存裝置與系統平台發生變異的機率非常高,將未來容易發生變異的部分抽離系統核心的商業邏輯,並且以穩固的介面與商業邏輯之間鬆綁,可以避免日後資料儲存裝置或者平台發生變動時,對系統本身的影響能夠降到最低。
其次,淬取出來的商業邏輯的再利用性也因此提高了,我們可以將之使用在其他類似的專案或者其他平台的版本上。而被分離出來的Data Access Layer也可以重複利用在其他專案上。
最後,這樣的架構下,也有利於我們做團隊分工的工作分派。
回到本篇文章的主題,Facade Pattern,這個架構跟Facade有什麼關聯呢?其實,這樣設計Data Access Layer的方式就是一種Facade Pattern的實作。
Facade的目的就是將複雜的介面簡化,將複雜與瑣碎的步驟封裝起來,對外開放簡單的介面,讓客戶端能夠藉由呼叫簡單的介面而完成原本複雜的程式演算。
看到這裡我們可以知道,其實Facade的概念可能早就深植在你心中,祇是你自己不知道而已。在了解Facade的觀念之後,我們可以更進一步將Facade發揚光大。
但是還是要提醒一件事情,任何Pattern都是有他的目的存在的,而且也不見得處處使用貫徹就一定是件好事,我們也要了解,當架構層次越是複雜,通常也代表會犧牲一部份的效能。
究竟要如何使用,端看設計師的經驗與現實考量了。
11 元月, 2006 10:36
在此之前我為各位整理一下在.NET中的介面與抽象類別兩者在實作時的相同及相異之處:
相同:
相異:
說到這裡我們先從程式語法上來看介面與抽象類別。
介面(Interface)
interface InterFace1
{
string Method1();
int Method2();
bool Method3();
}
抽象類別(Abstract Class)
public abstract class AbstractClass
{
public abstract string Method1();
public abstract int Method2();
public bool Method3(){bool a = false;
return false;}
}
各位應該可以看出其實兩者是很類似的,因為他們都包含了必須被客戶端類別程式實作的『抽象成員函式』,不過在介面中,每個成員函式都是需要被他的客戶端類別程式所實作的,而抽象類別中則可包含了一個不一定要客戶端類別程式實作的成員函數(非抽象成員函式),如這裡所看到的 Method3(),他已經有實作了該成員函式所需要的敘述內容也就是說可以預設給給他行為,這就是兩者不同點之一。不過若你把抽象類別的 Method3() 敘述內容拿掉,並在前面加上 abstract 的話,你會發現其實他跟介面是很類似的,若歸納來說其實介面可算是抽象類別的特例(special-case)。
由架構設計的觀念來說,使用抽象類別一定與繼承有關係,但一個客戶端類別程式只能存在一個繼承關係,然而一個客戶端的類別程式卻可以實作多個不同的介面,因此經常有人使用介面來解決多重繼承的問題。
再從另一個架構設計的角度來看,因為介面不能定義一些成員函式預設的行為,因此,若你恰好要設計給每一個實作的客戶端類別程式都有預設『共同』的行為時,此時若使用介面的話,你就必須一一的為實作過此介面的客戶端類別程式來新增同樣的程式內容,這是一件多麼費工的事情阿!而且不小心還會 Copy & Paste 錯了,相同功能的程式碼也會重複存放而不易維護呢!
若使用抽象類別時,你便可以應用他的特性,只要在客戶端類別程式的父類別(抽象類別),新增一個成員函式的預設行為便可。所以,在此之前你就必需考慮到所欲達到的功能是否有上述新增共用成員函式的必要性的必要性,才能審慎選擇到底是要用介面或者是抽象類別。這也說明了我所提到過的,在設計介面時要考慮到將來實作此介面的客戶端類別程式與介面本身之間是否具有高度的相關性,白話一點就是說你很難去解釋實作介面的客戶端類別程式是某個介面,這樣子的一個關係,如實作 .Net Framework 內建的 ICollecton 的客戶端類別程式時,你不能說他就是屬於 ICollection 這樣的本質關係,你只能說他『像是』一個ICollection,但是相反的若你可以解釋客戶端類別程式是某個類別時,就盡量修改成抽象類別來實作此功能,因為他們具有密切的相關性,在未來你想要新增共同的預設行為時,便不會干擾到使用了相同的抽象類別的『不同本質』之客戶端類別程式,而且可以避免上述的問題發生。
或許你還是存在很多的懷疑,所以接下來我拿網路上常看的的例子來為大家做更深入的陳述。現在我們考慮一個關於 Door 的概念,這個 Door 具有兩個行為方法分別是 Open() 與 Close(),我分別用介面與抽象類別來實作,
介面
interface InterFace1
{
object Open();
object Close();
}
抽象類別
public abstract class Door
{
public abstract object Open();
public abstract object Close();
}
以上介面與抽象類別其實是具備類似的功能。但現在問題來了,房子的主人要求你要增加警報的功能,若你是設計師我想你會很直覺的加上Alert()成員函式如下:
介面interface InterFace1
{
object Open();
object Close();
object Alert();
}
抽象類別
public abstract class Door
{
public abstract object Open();
public abstract object Close();
public abstract object Alert();
}
客戶端類別程式如下:
介面
public class AlarmDoor : InterFace1
{
object Open(){}
object Close(){}
object Alert(){}
}
抽象類別public class AlarmDoor : Door
{
public override object Open(){}
public override object Close(){}
public override object Alert(){}
}
但其實這種作法違反了物件導向的介面隔離原則(ISP),因為 Clients should not be forced to depend upon interfaces that they do not use,也就是說 Door 既有的功能Open() 及 Close() 其實是不會有太多改變的,且這樣的行為方法與 Alert() 在概念上來說是不應該混在一起的,因為若有些客戶端的類別程式是衍生或實作至 Door 時,若此時修改了Alert()的行為方法時,將影響到所有客戶端類別程式,因此盡量避免這樣的設計,以免除掉相互依賴性。
不過上面的敘述反映出兩個問題:
其實就 AlarmDoor 而言在本質上他是一個 Door 所以他可以是一個抽象類別,但 AlarmDoor 又有警報功能,就好像他可以完成一件警報工作,所以可以利用介面的方式來定義他。
public abstract class Door
{
public abstract object Open();
public abstract object Close();
}
public interface Alarm
{
object Alert();
}
public class AlarmDoor1 : Door, Alarm
{
public override object Open(){}
public override object Close(){}
public object Alert(){}
}
結論:
其實介面與抽象類別在設計時有個原則就是,介面隱含著 (like a) 的關係,而抽象類別存在著 (is a) 的關係,因此若你對於你要解決的問題本身有比較深入的瞭解後,不難分析出這兩者應用時的觀點,因此,只要多去思考問題本身的意涵後相信你應該可以迎刃而解,不再有疑惑了。
參考資料:
http://ccl.cis.nctu.edu.tw/class/ooad/MartinPrincipleSummary.htm
http://www.cnblogs.com/kavenmo/archive/2004/10/05/49124.html
08 元月, 2006 08:37
在物件導向設計中,抽象類別別與界面的相似性往往造成了設計師的困擾,進而在使用上產生混淆,本篇文章希望能夠透過幾個不同的觀點來描述這兩之間的差異性。
概念上的差異
我們先回到物件導向中關於類別繼承的基本解釋,子類別別與父類別別(註一)之間的關係可以說是一種「Is A」的關係。聽過的會覺得很正常,沒聽過的就會丈二金剛摸不著頭腦。
先舉一個簡單的例子,假設有一個叫做車子的類別,另外有一個叫做卡車的類別。如果卡車是繼承了車子類別別而成為車子的子類別別,我們可以就說卡車「Is A」車子,也就是說卡車是車子的一種。這樣講應該很容易理解。
那麼抽象類別別呢(Abstract Class)?原則上最簡單的解釋是,抽象類別別便是在領域概念中無法具體化的類別。
這樣講似乎還是很籠統,我們再舉一個簡單的例子。假設有一個叫做「形狀」的類別,而「圓形」類別與「方形」類別則是「形狀」類別的子類別別。在這個例子中,「形狀」很明顯的是一種抽像的概念,並無法具體化,因此在設計時,我們便可以選擇將「形狀」定義為一個抽象類別別。
由於「圓形」是「形狀」的子類別別,因此「圓形」具備了「形狀」的一切能力,如果是在繪圖軟體中,「圓形」便可由「形狀」中繼承到拉近的能力。在「形狀」中,設計師便可以先將拉近的能力細節將以描述清楚,「圓形」則可直接使用。
回過頭來,我們來思考界面(Interface)的用處。在概念上,我們可以說界面是一種可以附上在任意類別上的特徵,也是一種規範。
延伸上面的例子,假設「形狀」本身且不具備儲存的能力,我們想幫「圓形」加上這個能力,但是又不想「方形」具備儲存的能力。在這個狀況裡,在父類別別「形狀」中加上儲存的能力是不可行的,因為並不是所有圖形都需要可以儲存。
為了解決這個難題,我們最直覺的方式是在「圓形」中直接加上儲存的能力。理論上這樣是可行的,延伸思考是,如果還有其它的類別,例如「三角形」,也要加上這個能力時,會不會因為設計師的不同而讓兩個類別中的儲存的能力出現不一致,其實做的卻是同一件事情的狀況?這樣狀況就會有些失控了。
此外,假設儲存這個能力是需要與一個物件「大媽」(名字隨便取,不要想太多)相配合關連的,而該「大媽」不可能滿足所有類別實作儲存時的千奇百怪的設計方式,只能滿足一種。
那該怎麼辦才好呢?前面筆者提過了,界面也可以說是一種「規範」。
如果我們將儲存的能力定義在儲存的界面中,任何類別需要增加儲存能力時,只要遵循儲存界面的定義,那麼既可完成這個需求,又可以達到一致性。反過來說,在負責儲存的「大媽」中,他也只需要針對儲存界面來完成他的工作,他並不需要瞭解到底究竟有幾百個類別需要用到他來完成儲存這個功能。
如此豈不完美?
以上筆者用概念上的解釋來說明界面與抽象類別別在用途上的差異,下一篇(註二),我們將來研究如在實作上理解兩者的差異。
註:
一、父類別別是我的Partner建議的,我本來是想用母類別,我比較敬愛母親。
二、下一篇將由我的Partner負責執筆,敬請期待。
06 元月, 2006 18:49
哈囉,我們是.Net Go2 OO 研究室。
有鑑於物件導向的設計觀念日益普遍,但是許多MicroSoft.Net平台的開發人員仍然使用許多結構式架構在設計開發,因此我們希望能夠藉由一些心得與實例的分享,與大家交流物件導向的設計觀念。
當然學習是永無止境的,若是我們有任何謬誤的地方,也歡迎大家不吝指教,有交流才會有成長與進步。
日後我們也會將官方網站架設起來,會有更多資源在那邊提供大家使用。