1. 程式人生 > >設計模式解析第二版閱讀筆記

設計模式解析第二版閱讀筆記

設計模式解析第二版 俆言聲譯
Design Pattern Explained: A New Perspective on Object-Oriented Design, Second Editon. By Alan Shallway   and  James R. Trott
     ——書非借不能讀也
2013年11月19日06:00:01


前言
2013年11月19日06:09:45
如何使用共性與變性分析來設計應用程式的框架
測試是高質量編譯的一個優先原則
使用工廠例項化與管理物件至關重要


物件正在的威力並不在繼承,而在於自封裝。
多設計模式協同工作才能幫助我們建立更加健壯的應用程式。


從面向物件到模式再到真正的面向物件:學習模式本身,再學習模式背後的思想,再將理解擴充套件到分析與測試領域,也擴充套件到學習模式與敏捷程式設計方法之中

介面編譯(designing to interface),一個簡單的理解、使用原則是:一個物件只知道一個物件的介面,不需知道另一個物件的具體情況與就可以使用它

不能將設計模式作為一個單獨的東西使用,應該把它們結合起來使用,這樣才能做出好的設計:模式應該相互配合,共同解決問題。

Christopher Alexander的奇書The Timeless Way of Building告訴我們所有層次(分析、設計、實現)都存在模式。模式有助於理解問題域
在問題域中嘗試建立類,然後再將它們結合成一個系統,是Alexander認為是非常糟糕的方法。好的方式後面會介紹

通過學習設計模式,可以掌握基本的面向物件設計技術,能加深對面向物件中三大概念(封裝、繼承、多型)的理解

面向物件的設計:設計模式從分析到實現

作者:經曾以為設計模式之下所隱藏的執導性原則與策略在學習過一些設計模式之後已經非常清楚了,但設計模式所寫時的一些背景知識對Smaltalk社群來說是根深蒂固的,但對其他人則未必,要求你在讀四人幫的設計模式之前對面向物件範型有很深的理解。當我將四人幫的設計模式與Alexander的工作,Jim Coplien的共性與可變性分析的工作,Martin Fowler的方法學與設計模式的工作結合起來之後,那些指導性的原則才變得足夠清晰。

極限程式設計,測試驅動開發TDD及Scrum的經驗與設計模式也有極大的聯絡

無論在何種情況之下,都可以使用設計模式的指導性原則與策略來推匯出幾個設計模式,使用這些來得出設計模式更是一種思考過程的鍛鍊,使用這些來闡述設計模式及閱讀四人幫的設計模式書會有更深的理解,使用這些做出的設計大多數已經包含模式的精要在裡面了,不知道具體的設計模式的名稱也無關緊要,使用這些對面向物件分析和設計、對正確理解面向物件的原則都有好處


學習本書你將學習到模式為何有效,以及如何協同工作,以及模式背後的原則與策略

基於模式的分析能夠使我們成為更加高效和有效的分析人員,因為它使我們可以更加抽象的處理模型,因它它代表了許多其他分析人員的集體經驗;模式有助於人們理解面向物件的原理,並有助於我們處理物件的方式

基於模式的分析與設計:一個你還未知的領域,有空可以找一些書籍看一下


所有程式設計師的挑戰之一:不能過早開始實現,需要三思而後行

第一章
面向物件軟體開發簡介
2013年11月20日05:02:18

20世經80年代以來:“在需求列表中尋找名詞,並將它們轉化成物件”的指導原則。這種方式將封裝定義成資料隱藏,將物件定義成含有資料和用來訪問、操作資料行為的一些東西。其侷限性在於只觀注“如何實現物件”,在面向物件分析中損失了太多的內含。本章嘗試對這些概念進行擴充套件,並以此為基礎討論另一種面向物件正規化。這些定義都是源自設計模式研究的一些策略和原則的結果,反映了更完整的面向物件觀


面向物件範型是應對結構化程式設計遇到的諸多問題才應運而生的,理解這些挑戰有助於理解面向物件程式設計的優點

功能分解
功能分解:functional decomosition 是一種處理複雜問題的自然方法,主要做法是將問題分解成多個功能步驟(類似於演算法中的devide-and-conque),小問題更容易解決。
功能分解的一個問題在於“能者多責”,它通常導致讓一個主程式負責控制子程式,挑戰隨之而來:主程式能者多責、責任太多:要確保一切正常工作,還要協調和控制各函式的先後順序,因此經常會產生非常複雜的程式碼。
換一種思路來想問題:讓子函式負責自己的行為,主函式告知子函執行某些任務,並信任子函式知道如何進行,這種方式將比功能分解自然的多了。這其中一個重點的思想就是“委託 delegation”(我是這樣理解委託的:我告訴你我的目標,你來實現(使用什麼方法我不管))
功能分解的另一個問題在於“難以應對變化”,變化是無法避免了(各種維護需求,或者全新的需求都可能產生),經常要為已有主體增加新的變體以實現某種功能。為新需求修改目前執行良好的程式的後果就是,可能現有的程式也無法正常執行,bug隨之產生:“許多bug都源於程式碼修改”

思考:程式碼非要關注所有函式使用它們的方式嗎?函式應該怎樣與另一個函式互動?函式關注的東西是否太多,比如要實現的邏輯、要互動的東西、要使用的資料?和人一樣,如果程式試圖同時關注過多的東西,一旦有變化出現,就只能坐等bug的出來。

無論我們多麼努力,無論分析做的多好,也永遠無法從使用者那裡獲得所有的需求,因為未來有太多未知,萬物皆變化。我們無法阻止變化,但對變化本身卻並非無能為力。

需求問題
需求總在變化,原因多種多樣。我們要做到的:我可能無法知道什麼將變化,但是我能猜到哪裡會發生變化
“第二次既然總能編寫正確,你第一次也應該編寫正確”這句話並非要求預期所有可能發生的變化,並相應的構建程式碼,而是要求預期哪裡會發生變化,將這些區域給封裝起來,從而做到將程式碼與變化所產生的影響隔離起來,類似(A <---> 抽象層 <---> B),通過抽象層將變化隔離在一則,從而把變化所帶來的影響減小到最小。(記得沉思錄裡面好像有句話:“絕大多數問題都可以通過增加一個層來解決”,這個層當然就是指隔離變化的抽象層)

與其抱怨需求問題變化,不如改變開發過程,從而更有效的應用變化;程式碼可以設計的使需求的變化不至於產生太大影響,程式碼可以逐步演進,新程式碼可以影響較小的加入

應對變化:使用功能分解
與其編寫一個大函式,不如使之模組化,模組化肯定有助於提供程式碼的可理解性,從而提升程式碼的可維護性,但模組化並不有助於程式碼應對所有可能的變化。

作者:這種方式(當然是指功能分析了)我一直在用,發現有兩個主要問題:低內聚(weak cohesion)、緊耦合(tighg coupling)。
《Code Complete》 by Steve McConnell的精彩描述:
內聚性cohesion:一個例程(函式)內各操作之間聯絡的緊密程度。描述的是一個例程內各組成部分之間相互聯絡的緊密程度
耦合性coupling:兩個例程(函式)之間聯絡的緊密程度。描述的是一個例程和其它例程之間相互聯絡的緊密程度
軟體開發的目標應該是建立這樣的例程:內部完整(高內聚),而與其它例程之間的聯絡則是小巧、直接、可見、靈活的(鬆耦合)

修改一個函式甚至是一個函式使用的資料都可能對其它函式產生嚴重破壞,這種bug稱為“不良副作用”,即這些程式碼聯絡導致bug的生產。
維護和除錯的絕大多數時間都用在了:努力弄清程式碼的運作機理、尋找bug和防止出現不良副作用上,真正的修改時間相當相當的短,所以功能分解是注意力放在了錯誤的地方

應對需求變化
示例:假如你在一個會議上擔任講師,聽課的人在課後還要去聽其它的課,但他們不知道下一堂課的聽課地點,你的責任之一就是確保他們知道下一堂課去哪裡上。
可能的結構化程式設計方法,按步驟分解:
1. 獲得聽課人的名單
2. 對名單上的每一個人做如下工作:
2.1 找到他要聽的下堂課
2.2 找到該課的聽課地點
2.3 找到從你的教室到下一堂課的地點怎樣走
2.4 告訴這個人怎樣去下一黨課的教室
轉化為如下方法:
1. 獲得聽課人員名單的方法
2. 獲得每個眾課程表的方法
3. 告訴某個人如何從你的教室到其它任何教室的方法
4. 為聽課的每個人服務的一個控制程式

很少有人會這樣的做,你可能的做法:
1. 把下一堂課的課表、教室位置分佈圖貼出來
2. 讓每個人根據上述資訊,找到如何到下一個教室

兩種做法的區別在哪裡?
第一種做法:直接給每個人提供指示,你必須密切關注大量細節,除你之外沒有其他人負責,這樣你會瘋掉的。這裡你要負責一切。
第二種做法:只給出通用提示,然後期待每個人自己弄清楚怎樣完成任務。這裡學生對自己的行為負責。
這裡最大的區別就是責任的轉移,責任從自己轉移給每個人;要實現的目的相同,但是組織方式差異很大

為了看到責任轉移帶來的影響,我們考慮需求變化之後的修改情況:
新增需求:承擔助教的學生要在下一課之前收集本節課的學生評價表,並交到教師辦公室

第一種的修改:將不得不對控制程式進行修改以區分研究生和本科生,然後給研究生特殊指示,程式將做大幅度的修改
第二種的修改:在各司其職的情況下,只需要為研究生增加一個程式,而控制程式仍然只說“找到你們下一堂課的教室”,每個人以指示相應行事

這代表了控制程式的責任發生了明顯變化,第一種情況下,每增加一類學生,控制程式都要自身做相應修改,要負責每一類學生如何去做,並做相應控制;第二種情況不會影響控制程式的主體功能,由學生自己負責弄清自己如何去做。

第二種做法有如下三方面的不同:
1. 人們對自己的行為負責,不再由中央控制程式決定人們的行為
2. 控制程式與不同的人交流好像他們都是一樣的
3. 控制程式無需知道學生從此教室到彼教室可能需要採取的任何特殊步驟

《UML Distilled》 by Martin Fowler描述的軟體開發過程的三種不同視角:
1. 概念: “呈現了所研究領域中的各種概念。。。得出概念模型時應該很少考慮或者不考慮實現它的軟體。。。”,這個視角回答的問題是:軟體要負責什麼?
2. 規約: “現在考慮軟體,但是我們只關注軟體的介面,不關注實現”,這個視角要回答的問題是:怎樣使用軟體?
3. 實現: “這裡我們考慮軟體本身, 這可能是最常用的視角,但在許多方面,採取規約視角通常會更好”,這個視角要回答的問題時:軟體怎樣履行自己的責任

作為講師,你是在概念層次上與人交流,你告訴學生“你要他們做什麼”,而非“如何去做”。如何去下一課堂是明確的,是實現層次上的事情。在一個層次是進行交流,在另一個層次上執行,這樣請求者就無需知道具體的操作細節,只要一般性——概念性的知道即可。這一點的效力巨大:只要概念不變,請求者就與實現細節的變化隔離了。

面向物件範型
2013年11月20日07:02:39
面向物件範型以物件概念為中心,一切都集中在物件上,程式碼圍繞物件而非函式來組織。
那什麼是物件?
傳統定義:帶有方法的資料。這是一種非常糟糕的物件觀
新定義:具有責任的實體

使用物件的優點在於,可以定義自己負責自己的事物,物件天生就知道自己的型別。物件中的資料知道自己的狀態如何,物件的方法能夠使要求他做的事情得到正確執行

物件與責任:
1. Student 知道自己現在的教室,知道自己下堂課的教室;從一個教室去下一個教室
2. Instructor 告訴學生到下堂課的教室去
3. Classroom 有明確的地址
4. Direction giver 對於給定的倆教室,指出從一個教室到另一個教室的路線

這種情況下,物件還是通過尋找問題領域的實體發現的,然後分析這些物件需要做什麼,為每個物件明確責任,與在需求中找名詞發現物件和找動詞發現方法的技術是一致的。後面支給出更好的方法。

理解物件的最佳方式是將其看成是“具有責任的東東”。一條好的設計規則:物件應該自己負責自己,而且應該清楚的定義責任(定義概念、定義規約)。

使用Martin Fowler的視角框架來觀察物件:
1. 概念層次上,物件是一組責任
2. 規約層次上,物件是一組能被其它物件或其自己呼叫的方法(或稱行為)
3. 實現層次上,物件是程式碼與資料,以及它們之間的相互計算

糟糕的是,在面向物件設計的教學與討論中更多的是停留在實現層次上,而略去了前兩者的巨大效力(思考:這也不能太怪面向物件課程的教授者,他在教授學生基本的面向物件的語法,不是在教如何做好軟體的設計,如何這位老師有意識的先從概念與規約視角來講物件,再從實現的視角來講物件,那自然最好,但是在學生都不知物件的語法的情況下,講這些概念,是不是就強人所難了)


2013年11月22日05:51:09
物件提供給外界的,要求物件做什麼的方法的集合稱為物件的公開介面 public interface

類就是物件行為的定義,它包含如下完整描述:
1. 物件所包含的元素
2. 物件提供的方法(為完成某種責任的對外介面,及完成責任所需的一些內部方法)
3. 訪問這些資料元素和方法的方式


型別(type), 例項(instance),建立類的例項的過程稱為例項化(instantiation)

面向物件方法的“去下堂課教室”:
1. 開始控制程式
2. 例項化教室中學生的集合
3. 告訴此集合,讓學生到自己下堂課的教室
4. 集合讓每個學生自己去下堂課的教室
5. 每個學生都:
5.1 找到自己下堂課的教室在哪裡
5.2 決定怎樣去(步行、自行車、校車、開車、把自己郵寄過去。。。都行)
5.3 去那裡
6. 完成


抽象類(abstract class)定義了【其它】一些相關類的行為,或者理解為抽象類定義了一組類可以做什麼。實現角度來講,【其它】類通常是抽象類的子類,稱為具體類(concrete class),它通常代表著一個概念特定、不變的實現

is-a(是一個/種)關係是稱之為繼承(inheritance)關係的一種特例, 如上面講的RegularStudent(普通學生,這裡充當具體類),GraduateStudent(研究生,這裡充當具體類),都是Student(學生類,這裡充當抽象類)的子婁,稱具體類是特化(specialize)了抽象類,稱抽象類泛化(generalize)了具體類

從概念視角、規約視角來看待抽象類遠比從實現視角看待抽象類要重要的多,把抽象類理解為其它類的佔位符。可以使用抽象類來定義其派生類必須實現的方法。抽象類還可以包含派生類都能夠使用的方法,派生類是使用抽象類的預設行為還是使用自己有所變化的行為,由派生類自己決定(這與“物件自己負責自己”的要求一致)

抽象類不只是不能例項化:抽象類通常被定義成“不能例項化的類”,這個定義本身沒有錯——在實現層次上來講。但是侷限性太大,從概念層次上把抽象類理解成其它的佔位符對設計模式的理解更有幫助。也就是說,抽象類為我們提供了一種方法,能夠給一組相關的類賦予一個名字,這使我們可以將這一組相關的類看成一個概念。 在面向物件範型中,必須從概念、規約、實現三個層次來思考問題

物件都自己負責自己,所以很多東東不需要暴露給其它物件,公開(public)介面——可被訪問的方法,相應的不能訪問的方法與資料則是私有(private)的,面向物件語法中區分上述可訪問性為如下三種類型:
1. 公開(public)——任何物件都可以看見
2. 保護(protected) ——只有這個類及其派生類的物件可以看見
3. 私有(private) ——只有這個類的物件可以看見

這就引出了封裝(encapsulation)的概念,封裝通常簡單的描述成“資料隱藏”,也就是說不應該暴露給外界的內部資料成員的可見性為protected或private(其它書裡面多提倡:“優先使用組合而不是繼承”的原則,這樣一個物件只應該有public與private的可見性,protected的可見性其實是派生類對其父類資料的可見性的侵犯,在設計時應該儘量沒有protected可見性的資料成員與方法。即使使用派生,也完全可以為資料提供訪問介面的形式來進行,派生這一作法應該只發生在具體類對抽象介面的派生(這裡介面理解為 java中的接口才對,或者是c++中的只包含純虛介面的類),不應該出現具體類對具體類的派生(具體類這裡指有資料成員的類))

封裝不只是資料隱藏,封裝意味著各種隱藏。抽象類隱藏了從其派生的類的型別

另一個重要的概念是多型(polymorphism): 抽象類的方法對不同具體類有不同的行為,這種情況稱之為多型。具體的語法見面向物件教程課本,這裡想說的是多型從概念與規約視角來看所帶來的價值,就是讓使用者根據呼叫介面(規約),可以概念性的操作物件


封裝有幾個優點,“對使用者隱藏”這一事實直接蘊涵瞭如下優點:
1. 使用更加容易,使用者無需操心實現問題
2. 可以在不考慮呼叫者的情況下進行修改,呼叫者從一開始就不應該知道物件是如何實現的,它們之間應該只有介面信賴,不應該有任何其它信賴關係(介面不變,則呼叫者的使用方式可以不變)
3. 其他物件對該物件內部是未知的——這些內部資料用來幫助物件實現其向外承諾的功能


再次考慮不良副作用的問題,這種bug通過封裝有效的解決了。物件對其它物件的內部是未知的,使用封裝,並遵循“物件自己負責自己”的策略,那麼唯一能改變物件的方式就是:呼叫該物件的對外介面中提供的方法。物件的資料和實現其責任的方法,都會與其它物件所帶來的變化遮蔽開

物件拯救了我們:
1. 物件對自己行為所負的責任越多,控制程式需要負的責任就越少
2. 封裝使物件內部行為的變化對其它物件變得透明
3. 封裝有助於防止不良副作用

封裝什麼東東時,必然將使其耦合變鬆,隱藏實現(即封裝它們)有助於鬆耦合


特殊物件方法
建構函式(constructor)和解構函式(destructor)是在物件起始與結束時做一些工作,這是物件“自己負責自己”所要求的

第五章
設計模式簡介
2013年11月25日05:50:29

學習設計模式對加深面向物件分析與設計的理解大有裨益

本章很多思想來自:Christopher Alexander的 The Timeless Way of Building 一書

在一種文化中,不同的人在很大程度上可以就“何為優秀的設計,何為美”之類的問題達成共識,文化對優秀設計的評價將超越個人的看法

文化學的一個重要分支,就是在尋找描述一種文化的行為與價值觀的模式

設計模式背後的一個觀點就是:軟體系統的質量可能客觀度量

怎樣著手進行優秀的設計?
1. 在優秀設計中具有而在劣質設計中不具有的東東是什麼?
2. 在劣質設計中具有而在優秀設計中不具有的東東又是什麼?

Alexander的如下信念:如果設計的質量可以客觀的評價,那麼我們應該可以找出設計因何優秀,又因何拙劣的評價標準

在進行了大量觀察之後,Alex發現在特定的建築物中,優秀的結構都有一些共同之處 (幸福總是相似的,而不幸的原因則各有各的不同),即要在好的設計中尋找共性

Alex發現,通過觀察解決相似問題的不同結構,可以看出優秀設計之間的相似之處,他將這種相似之處稱為模式

模式:某一背景之下,某個問題的一種解決文案。每個模式都描述了一個在我們的環境中不斷重複出現的問題,並進而敘述了這個問題解決方案的要素,通過這種方式,解決方案能夠百萬次的反覆應用,但是具體方式又會完全不同

一個模式的描述應該包括4項:
1. 模式的名稱
2. 模式的目的,即要解決的問題
3. 實現方法
4. 為了實現模式我們必須考慮的限制與結束因素

幾乎任何設計問題中都存在模式,而且可以結合起來以解決複雜問題


從建築模式到軟體設計模式
1. 軟體中是否存在不斷重複出現,可以以某種相同的方式解決的問題?
2. 是否可以使用模式方法來設計軟體,即先找出模式,然後根據模式來建立特定問題的解決方案?
這倆問題的答案,毋庸置疑都是肯定的,那接下來的事情就是找出一些模式,然後制定出新模式的編錄標準,應運而生的就是Gof的《設計模式:可複用面向物件軟體的基礎》

本書有如下目的:
1. 將設計模式的思想應用於軟體設計——並稱它們為設計模式( design pattern )
2. 給出編錄和描述設計模式的一種格式
3. 編錄了23個設計模式
4. 在這些設計模式的基礎上推匯出一些面向物件設計的策略與方法

Gof並沒有建立書中的模式,相反他們只是將軟體界已經存在的、反映了(針對各種具體問題的)優秀設計經驗的模式識別出來,這與Alex的工作類似。(與模式識別很相似哦^_^)


模式的關鍵特徵:
1. 名稱: 每個模式都有唯一的用於標識自己的名稱
2. 意圖: 模式的目的
3. 問題: 模式要解決的問題
4. 解決方案: 模式怎樣為問題提供一個適合其環境的解決方案
5. 參與者與協作者: 模式所涉及的實體
6. 效果: 使用模式的效果,研究模式中起作用的各個因素
7. 實現:模式的實現???,這裡實現只是模式的具體實現,並不能視為模式本身
8. 一般性結構: 顯示模式典型結構的標準圖

設計模式中的術語“後果(consequence)”這個詞是指因果中的效果而已,也即,你???實現的模式,它將怎樣影響已有的因素,又會怎樣被已有因素所影響

為什麼學習設計模式
設計模式有助於複用與溝通,模式還為我們提供了觀察問題、設計過程和麵向物件的更高層次的視角,將使我們從“過早處理結節”的桎梏中解放出來,改變人的思維定式,從而成為更加高效的分析人員

學習模式的其他好處
1. 改善團隊的溝通與個人學習
2. 程式碼更易於修改與維護
3. 設計模式闡述了基本的面向物件原則
4. 採用更好的策略,即使不使用模式

設計模式一書對優秀的面向物件設計的策略提出的一些建議:
1. 按介面程式設計(designing to interface)
2. 儘量用聚合代替繼承
3. 找出變化並封裝之(在我現在的理解就是應該增加一個抽象層來封裝變化,使變化被隔離在一則,另一則針對介面程式設計,從而使變化的影響減少到最小)

第八章
開拓視野
2013年11月26日06:33:12
本章討論面向物件的三個基本概念:物件、封裝與抽象類
本章嘗試從理解設計模式的角度出發,描述一種全新的看待面向物件設計的方式,及高質量程式碼的本質品質


物件的傳統看法與新看法
傳統:具有方法的資料
新看法:具有責任的實體
“開始時尋找描述問題領域中狀態的資料,然後新增處理資料的方法,瞧,物件就來了”,這個方式太簡單與膚淺了,這種全從實現的視角來看待物件的方式不可取

更有意義的方式應該從概念視角出發來看待物件——具有責任的實體。這種責任定義了物件的行為。這種方式有利於我們關注物件的意圖行為,而非如何實現物件。使我們把構建軟體拆成兩步:
1. 先做出一個初步設計(如巨集觀架構的整體框架),不用操心所有相關細節
2. 逐步細化,以實現該設計

不過早的操心實現細節,設計出的總體架構應該更加穩定,實現細節通過封裝隱藏起來,更利於框架的穩定

這種方式行之有效,是因為我們只關注物件的公開介面,這是我們要求物件完成某些工作(完成責任)的交流渠道。有了好的介面,我們能要求物件完成其責任範圍之內的任何工作,而且相信其能夠完成的很好,我們不需要知道在物件內部到底是怎樣執行的,不需要知道它怎樣處理我傳遞給它的資訊,也不需要知道它怎樣收集其他需要的資訊,我們大可以放心的【全權委託】給它

如有一個Shape物件,有如下責任:
1. 知道自己的位置
2. 能夠在顯示器上繪製自己
3. 能夠從顯示器上擦除自己

這些責任意味著如下方法:
1. getLocation(...)
2. drawShape(...)
3. unDrawShape(...)

關注動機而非實現,是設計模式中反覆出現的主題。因為實現隱藏在介面之後,實際上將物件的實現與使用它們的物件解耦了


封裝傳統看法與新看法
傳統看法:封裝即資料隱藏
汽車能夠起到傘的遮風蔽雨的目的,如果從中這一個角度來把洗車當成傘看待,那侷限性就太大了;同樣,封裝也不僅僅是資料隱藏,這樣看待侷限性也太大了。
新看法:封裝應該被視為“任何形式的隱藏”,除資料隱藏之後外還可以隱藏如下東東:
1. 實現細節
2. 派生類
3. 設計細節
4. 例項化規則

隱藏實現其實是封裝了實現細節;型別的封裝通過多型使用具有派生類的抽象類(或者具有多種實現的介面)實現的,(抽象類的使用者無需知道派生類的實際型別,這正是《設計模式》一書中封裝的通常含義);


這種更加寬泛的方式看待封裝,其優點是能夠帶來一種更好的切分(即分解)程式的方法。封裝層就成為設計需要遵循的介面。

不從純虛介面繼承可能存在的三個問題(即從一個已經實現好了的、正常工作的類中繼承):
1. 可能導致弱內聚:類的主體功能在派生類中又要關心一些細節等
2. 減少複用的可能性:為主體功能增加的程式碼,只包含在這個類裡面,不如把其單獨成類,以便複用
3. 無法根據變化很好的伸縮:新需求進來又如何,再次派生新的類嗎?不如新功能是一個單獨的類,使用組合或裝飾的方式新增新的需求,這種方式還可以做到執行時動態組合新增

另外一種方式:將類按相同行為分類

2013年11月27日05:28:17
發現變化並將其封裝

《設計模式》一書有如下建議:考慮你的設計中哪些地方可能變化。這種方式與關注會導致重新設計的原因相反,它不是考慮什麼會迫使你設計改變,而是考慮你怎樣才能在不進行重新設計的情況下進行改變。這裡的關鍵在於封裝發生變化的概念,這是許多設計模式的主題。簡而言之:“發現變化並將其封裝”

將封裝看成是通過抽象類或介面隱藏類——“型別封裝”,一個物件聚集了這種抽象類的引用,這些引用其實隱藏了表示行為變化的類。

事實上,很多設計模式都是使用封裝在物件之間建立一個【層】。這樣設計者可以在【層】的兩側進行修改,並不會對另一側產生不良影響,做到了兩側的鬆耦合。

事例:對動物的不同特徵建模。專案的需求如下:
1. 每種動物都有不同數量的腿
2. 動物物件必須能夠記住並獲取這一資訊
3. 動物的移動方式可以不同
4. 對於給定的地形型別,動物必須能夠返回從一個地方移動到另一個地方所花費的時間

可能的設計:
1. 腿的數量使用一個數據成員存放
2. 假設有兩種不同的移動方式,這是處理行為上的變化,通常採用另一種方式:a. 使用一個數據成員說明物件有哪種移動方式,程式碼中使用if/else來進行區分。 b. 使用兩種類表示,一種能夠行走,一種能夠飛翔

糟糕的是,當問題更加複雜時這兩種方式都不可行。雖然這種方法在只有一種變化(移動方式)時能夠奏效,但如果存在更多變化時會怎樣呢(如再增加食性(草食性與肉食性的區分))?如果對於每種特殊的情況都使用繼承,會生成很多類。使用一種開關變量表示動物的型別,將移動方式與食性結合起來,降低了物件的內聚性。再考慮如下新需求:一種動物在不同情況下會表現出不同的行為,又該如何?

另一種設計:讓動物類包含一種合適移動行為的物件,即對於移動方式這種變化提取一個介面(抽象類)(假設的兩種移動方式可為兩種不同的子類),對於食性這種變化點也提取一個介面(抽象類)。即為物件(動物)增加移動方式的資料成員(物件),增加食性的資料成員(物件)。從中可以看出使用組合(或稱聚合)優於使用繼承。

用物件的屬性來包含變化,和用物件的行為來包含變化其實非常相似,從一個數據成員變成一個類來封裝行為的變化看似並沒有太大的改變,但這種處理方式對於“物件應該自己負責自己”有好處,變化在持續,把很多程式碼都寫在一個類中,複雜性將急劇上升,維護成本將越來越大。

將繼承視為一種一致的處理概念上相同的幾個具體類的方法,比看成一種特化方法要合理的多。

2013年11月27日06:18:15
共性與可變性分析與抽象類
Jim Coplien有關共性與可變性分析的工作,告訴了我們如何在問題領域找到不同的變化,如何在不同領域找到共同點:找到變化的地方,稱為“共性分析”; 然後找出如何變化,稱為“變性分析”

按照Coplien的說法:“共性分析就是尋找一個共同的要素,它們能夠幫助我們理解系列成員的【共同之處】在哪裡。”。這裡的【共同之處】指通過表現方式或者所執行的功能相互關聯的一些要素。找出事物共同點的這一過程將定義這些要素所屬的系列。如記號筆,鉛筆,圓珠筆都識別成“書寫工具”的共性

可變性分析:揭示了系列成員之間的不同。可變性分析只有在給定了共性之後才有意義:
共性分析尋找的是不可能隨時間而改變的結構,而可變性分析尋找的是可能變化的結構。可變性分析只有在相關聯的共性分析定義的上下文中才有意義。。。從架構的視角看,共性分析為架構提供長效的要素,而可變性分析則促進它適應實際使用所需。

也就是說,如果變體是問題領域中各個特定的具體情況,共性就定義了問題領域中將這些情況相互聯絡起來的概念。共通的概念用抽象類表示。可變性分析所發現的變化將通過具體類實現。

三方面的聯絡:
共性分析  ->  概念視角  ->  抽象類

              規約視角

可變性分析 -> 實現視角  ->  具體類

說明:
1.通過分析這些物件必須完成哪些介面(概念視角),我們能夠確定如何呼叫它們(規約視角)。
2.在實現這些類的時候,要讓API提供足夠資訊,能夠保證正確實現而且解耦
3.規約視角位於其中,共性與可變性都要涉及的視角,規約描述瞭如何與概念上相似的一組物件通訊。在實現層次上這個規約將成為抽象類或者介面。


使用抽名類進行特化的好處:
1.抽象類與核心概念:抽象類代表了將所有派生類聯絡起來的核心概念。正是這些核心概念定義了派生類的共性
2.共性與要使用的抽象類:共性定義了需要使用的抽象類
3.可變性與抽象類的派生類:從共性中識別出來的可變性將成為抽象類的派生類
4.規約與抽象類的介面:這些類的介面對應於規約層次


這樣類的設計就簡化成如下兩個步驟:
1.定義抽象類(共性)時,必須問自己:需要用什麼介面來處理這個類的所有責任
2.定義派生類(可變性)時,必須問自己:對於這個給定的特定實現(這個變化),應該如何根據給定的規約實現它

規約視角與概念視角之間的關係在於:規約標識了用來處理這些概念所有情況(即概念視角所定義共性)所需的介面
規約視角與實現視角之間的關係在於:對於規定的規約,怎樣實現這個特定情況(這個變化)?


敏捷程式設計的品質
2013年11月28日06:16:55
設計模式常被人敘述成需要“預先設計”,它提倡從問題領域的一些主要概念著手,然後深入,考慮更多的細節。
極限程式設計(eXtreme programming XP)提倡另一種方法,它的核心是循序漸進的開發,在程式設計的同時進行驗證,大的概念是從眾多小的概念中演變出來的

極限程式設計似乎不提倡預先設計,而設計模式則要求預先設計,但兩者並不是對立的,相反的其是相輔相成的,殊途同歸的目的:高效、健壯和靈活的程式碼。

極限程式設計包含的一些品質(順序與重要性無關):
1.無冗餘
2.可讀
3.可測試


無冗餘
某個規則只在一個地方實現,或稱“一個規則,一個地方”,或稱“一次且僅一次規則”,代表了設計人員的一個最佳實踐。這消除了重複,從而避免的很多可能的問題。冗餘與耦合之間常常糾纏不清。

遵循“按介面設計”做法,找出變化之處,從而使程式碼高效內聚,是消除冗餘程式碼所必須的,級限程式設計中無冗餘,避免耦合,要求程式碼被封裝在定義明確的介面之後。

可讀性
與強內聚息息相關。“按意圖程式設計”將這一品質提升一個新的高度。給函式指定一個“反映意圖的名字”,程式設計變成了對一系列函式的呼叫,這些函式的命名清楚的說明的它們的意圖。在更大的層次上,閱讀者要看的是程式碼的意圖,而不是程式碼的實現。Martin Fowler寫到:“每次當我們感到什麼東西需要加註釋的時候,相反的,我們會編寫一個方法”,其結果就是更簡短,更清晰的方法。
按意圖程式設計與按介面程式設計又是英雄所見略同

可測試性
這是級限程式設計的核心。編寫程式碼之前就先編寫測試,有幾個目的:
1. 最後能得到一組自動化測試
2. 必須是按方法的介面而非實現來設計,從而得到內聚性、封裝性更好,而耦合性更鬆的方法
3. 關注測試你會將這些概念分成可測試的部分,從而得到強內聚鬆耦合的程式碼

我們稱容易測試的程式碼為可測試程式碼。“預先編寫測試”的實踐,本質上是會產生可測試性很高的程式碼。

許多開發人員將這種理念繼續推進,通過測試驅動整個開發過程,這種方法稱之為測試驅動開發(Test-Driven Development TDD),它乍看起來與模式要求互不相容,實則不然,其與模式基於相同的原則,只是處理程式碼編寫任務的方式不同罷了。

第12章
專家設計之道(些專家非彼磚家)
2013年11月28日07:02:29
怎樣著手設計?是獲取細節,再看它們如何組合在一起?還是先從總體概念(big picture)開始, 再將其逐步分解?

Chirstopher Alexander的方法是先把注意力放到高層的關係上——某種意義上的自頂而下。他認為做出設計之前,理解所要解決的問題的背景至關重要。他用模式來定義這些關係。他不僅提出了一些模式,還給出了一整套設計方法。

Alex:設計常常被認為是一種合成過程,一種將事物放到一起的過程,一種組合的過程。按照這種觀點,整體是由部分組合而成的。先有部分,然後才有整體的形式。

從部分構造整體,不可能等到優美的設計。

對於軟體開發人員來說,這些部分就是物件與類,找出物件與類,並它們定義行為與介面,但不能只把注意力放在部分上,把預先形成的部分加起來,不可能形成自然特徵的任何東西,(即把預先建立好的一些例程庫加起來無法獲得優秀的軟體設計)Alex認為從部分構造並非好的設計方式。

2013年12月2日06:07:23
Alex這裡的“模組化”是指建築業中的那些完全相同、可以互換的部分,不是指軟體業中的術語“模組”,他的意思是,如果在形成總體概念之前就開始構建模組,這些模組不可能有能力處理特殊的需要。遵循了Alex的方法,我發現建立的類比通用部分出發所得到的更好,而且可用性更強。

我們的目標是在它們所處的大背景中設計部分——類與物件,從而構建出健壯而靈活的系統。軟體的設計看成是一種【複雜化】(complexification)的過程更好。
【複雜化】:結構是通過對整體操作、使其起皺(crinkling it)而注入其中的,而不是通過一小部分一小部分新增而成。在分化的過程中,整體孕育了部分,整體的形式及其各個部分是同時產生的。

Alex所描述的是這樣一種設計思想:開始用最簡單的術語考慮問題(概念層次上),然後新增更多特性,在此過程設計將越來越複雜,因為我們加入了越來越多的資訊。

每一個模式都是一個分化空間的操作符,也就是說,它在以前沒有差異的地方建立差異。語言就是一系列這樣的操作符,其中每一個操作符進一步對以前分化形成的意象繼續分化。

Alex斷言:設計應該從問題的一個簡單陳述開始,然後通過在這個陳述中加入資訊,使它更加詳細(也更回覆雜)。這種資訊應該採取模式的形式。對於Alex來講,模式定義了問題域中實體之間的關係。

通過考慮實體互相之間關係應該怎樣,為設計提供了大量資訊,在考慮模式背景中存在的其它模式,可以改進設計。

Alex指出優秀設計師(當然是批建築中的,是否可能擴充到其它領域?)應該遵循的規則:
1. 每次一個:模式應該按順序每次只運用一個
2. 背景優先:首先應用那些要為其它模式建立背景的模式


Alex所描述的模式定義了問題域中實體間地的關係,這些模式的重要性次於這些關係,但它們為我們提供了討論這些關係的方式。

Alex的步驟討論:(《建築的永恆之道》)
1. 找出模式:用這些模式思考問題,要記住,模式的用途是定義實體之間的關係
2. 從背景模式開始:為其它模式建立背景的模式應該為設計起點
3. 然後從背景轉向內部:觀察其餘的模式或任何其它可能已經發現的模式,應該第二點,重複這一過程
4. 改進設計:改進過程中始終考慮模式所蘊涵的背景
5. 實現:實現應該融入模式所要要求的細節

通過在已經出現的概念的背景中新增新概念進行設計,應該是我們從Alex的工作中至少要學習到的一點。


用模式解決CAD/CAM問題
2013年12月3日06:45:58

問題回顧
基本需求:建立一個讀取CAD/CAM模型(系統現有V1版本,並會有新版本加入V2,及將來可能的V3)並從中提取部分的計算機程式,將這些部件提供給一個現成的專家系統,從而進行智慧化設計。複雜之處在於提取程式要與多個版本的CAD/CAM系統互動

系統如下表中的需求:
1. 讀取CAD/CAM模式並提取部件 :【補】
2. 能夠處理多種零件:
3. 處理多個版本的CAD/CAM系統:

用模式思考
模式為我們的思考指引了方向,用在問題域中發現的那些模式進行思考,只考慮關鍵的概念,先不要操心細節。

用模式思考的過程
1. 找出模式 : 【補】
2. 分析與應用模式
2.1 按背景的建立順序將模式排序
2.2 選擇模式並擴充套件設計
2.3 找到其它模式
2.4 重複
3. 新增細節

設計模式最有用的地方,可能就是提供了著物的方法,此後通過找出問題領域中各種概念之間的關係,充實其它部分,這就需要使用共性/可變性分析(見下面章內容)

找出模式
問自己:約束因素有哪些?問題域中的問題何在?它們與我們知道的模式如何對應?

按背景考察模式
背景和一種定義是:一些事物存在或發生所處的相互有關的情況——一種環境、一種場景
系統中一種模式經常與其它模式相關——為系統中的其它模式提供背景。在分析中,尋找一個模式與其它模式是否相關、如何相關、尋找這個模式為其它模式建立或提供的背景,以及這個模式自身存在的背景,都是非常重要的。也許這些並非每次都能找到,但是通過尋找你能得到更加高質量的解決方案

尋找背景是一種非常基本的工具,確定哪個模式為哪個模式建立背景,經常要藉助於模式的概念性考察,稱這些為械的“元”描述

考慮背景時使用的一條規則:在知道所需物件是什麼之前,無需考慮如何例項化這些物件的問題。在設計中需要儘量減少腦子裡思考的事情。我們應該確定了物件是什麼之後再定義工廠

【最高模式】將約束其它模式,【最高模式】是指系統中為其它模式建立背景的一兩個模式。或稱“最外層模式”或“背景設定模式”

一個可選的尋找方法:
1. 有沒有一個模式定義了另一個模式的行為?
2. 有沒有兩個模式彼此相互影響?

使用械還另一個優點:可以看出重用與靈活性,而從細節開始著手的設計,如果最終組合到高層做的不好,將很難重用

從總體概念開始,然後不斷新增特性。通過分析找出系統的最高模式,注用程式的框架基本就出來了。【補細節】

未完待續。。。