大B:“我來講講我個人對設計模式的理解吧。”
小A:“呵呵!好啊!”
大B:“也許能讓你更好地理解23種設計模式。”
1、Adapter(介面卡)模式:旨在提供使用者期望的介面,以便利用具有不同介面的類的服務。
(1)個人理解:實際上只是把客戶呼叫,轉變為呼叫已經存在的方法。介面卡的作用可以理解為提供一個人人皆知的,顧名思義的新方法名。
(2)提示程式碼:
publicdoublegetMass(){
returnrocket.getMass(simTime);
}
2、Facade(外觀)模式:旨在為子系統提供一個介面,使之更加容易使用。
(1)經典範例:JOptionPane類,JOptionPane.showConfirmDialog(……)
(2)個人理解:構建一個個目的明確的類,比如典型的靜態方法的使用。
(3)提示程式碼:
intoption;
option=JOptionPane.showConfirmDialog(……);//靜態方法建立對話方塊
(4)提示關鍵字:外觀類,工具類,例項類
3、Composite(組合)模式:旨在讓使用者能夠用統一的介面處理單個物件以及物件組合。
(1)經典範例:組合,樹,環
(2)個人理解:其他很多模式的基礎,群組可以包含群組或者個體,群組和個體有共同的介面。
(3)提示程式碼:
MachineComponentmc=(MachineComponent)i.next();
count+=mc.getMachineCount();
(4)提示關鍵字:遞迴
4、責任型模式Bridge(橋接)模式:旨在將依賴抽象操作的類與這些抽象操作的實現相分離,從而使得抽象類與實現能夠獨立變化。
(1)經典範例:驅動程式
(2)個人理解:將抽象和方法的具體實現分離,抽象類中包含一個driver物件,driver物件即是對方法的具體實現。
(3)提示關鍵字:裝載
5、ChainofResponsibility(責任鏈)模式:旨在將一個方法呼叫請求沿著責任鏈依次轉發給下一個物件,讓每個物件都有一次機會決定自己是否處理該請求,從而降低請求的傳送者與其接受者之間的耦合程度。
(1)個人理解:尋找責任的請求在鏈中傳遞,如果責任人已經找到則終止,否則繼續向其他物件轉發責任。
(2)提示程式碼:
publicEngineergetResponsible(VisualizationItemitem){
if(iteminstanceoftool){
Toolt=(Tool)item;
returnt.getToolCart().gerResponsible();
}
if(iteminstanceofToolCart){
ToolCarttc=(ToolCart)item;
returntc.gerResponsible();
}
}
(3)提示關鍵字:轉發
6、Singleton(單例)模式:旨在確保某個類只有一個例項,並且為之提供一個全域性訪問點。
(1)個人理解:建立一個類的唯一例項,可以作為全域性變數。
(2)提示程式碼:
publicstaticFactorygetFactory(){
if(factory……null)
factory=newFactory();
returnfactory;
}
7、Observer(觀察者)模式:旨在在多個物件之間定義一對多的依賴關係,以便當一個物件狀態改變時,其他所有依賴這個物件的物件都能夠被通知,並自動更新。
(1)經典範例:GUI(MVC中分離M和VC)
(2)個人理解:當一個物件發生改變的時候,其他關心該物件的物件能夠得到通知,並且更新自身狀態。
(3)提示程式碼:
publicvoidnotifyObservers(){
observers.update();
}
(4)提示關鍵字:註冊,監聽
8、Mediator(中介者)模式:旨在定義一個物件來封裝一組物件之間互動的方式,這樣可避免物件間的顯示引用,而且還可以獨立對這些物件的互動進行修改。
(1)經典範例:GUI(特指MVC中的controller)
(2)個人理解:中介者類專門用於處理物件間的互動,與GUI的佈局元件分離
(3)提示程式碼:
publicvoidsetLocation(Machinevalue){
returnmediator.set(this,value);
}
9、Proxy(代理)模式:旨在為某個物件提供一個代理來控制對該物件的訪問。
(1)經典範例:影象代理(長時間載入記憶體前的Loading提示)
(2)個人理解:提供一個代理來承擔責任(轉發請求),實際操作的物件並不是根本物件,而是一個使用者和真正實現之間的中間角色。
(3)提示程式碼:
setImage(LOADING.getImage());
callbackFrame.repaint();
newThread(this).start();
(4)提示關鍵字:佔位
10、Flyweight(享元)模式:旨在透過共享來為大量的細粒度物件提供有效的支援。
(1)個人理解:很多類具有相同的且不變的屬性,可以將這些屬性提取出來構成享元,在一個特定的工廠類中作為內部類,具有static的get方法,便於外部類共享。
(2)提示程式碼:
publicclassChemicalFactory{
privatestaticMaochemicals=newHashMap();
ChemicalImp{
//someattributesandmethods
}
static{
chemicals.put(newChemicalImp());
}
publicstaticChemicalgetChemical(Stringname){
return***;
}
}
(3)提示關鍵字:共享物件
11、Builder(生成器)模式:旨在把構造物件例項的程式碼邏輯移到要例項化的類的外部,以便於細化構造過程,或者簡化物件。
(1)經典範例:解析文字構造物件
(2)個人理解:用一個builder類收集構造資訊,在確定資訊足夠(或者滿足構造的最低要求)的時候,再生成物件。
(3)提示程式碼:
Stringsample=“*****”;
ReservationBuliderbuilder=newUnforgivingBuilder();
newReservationParser(builder).parse(sample);
Resercationres=builder.build();
(4)提示關鍵字:逐步構造
12、FactoryMethod(工廠方法)模式:旨在定義一個用於建立物件的介面,同時控制對哪個類進行例項化。
(1)經典範例:迭代器
(2)個人理解:為相關的多個類提供一個共同的介面,客戶不需要知道該例項化哪個類,具體例項化的類由服務的提供者決定。
(3)提示程式碼:
Listlist=Arrays.asList(newString[]{“1”,“2”,“3”});
Iteratoriter=list.iterator();
(4)提示關鍵字:共同介面
13、AbstractFactory(抽象工廠)模式:旨在建立一系列相互關聯或相互依賴的物件。
(1)經典範例:GUI工具包
(2)個人理解:建立一系列相關的物件,也就是把建立一個大物件所需要的子操作聚合起來。
(3)提示程式碼:
publicJButtoncreateButtonOK(){
JButtonb=super.createButtonOk();
b.setIcon(getIcon(“images/123.gif”));
returnb;
}
(4)提示關鍵字:外觀和感覺
14、Prototype(原型)模式:透過複製一個現有物件生成新的物件。
(1)個人理解:透過複製一個已經存在的物件,儲存原來物件的狀態,在此基礎上進行進一步的改動。
(2)提示程式碼:
publicOzPanelcopy2(){
OzPanelresult=newOzPanel();
result.setBackground(this.getBackground());
//moreresult.set***methods……
returnresult;
}
(3)提示關鍵字:複製
15、Memento(備忘錄)模式:旨在為物件提供狀態儲存和狀態恢復功能。
(1)經典範例:撤銷操作
(2)個人理解:使用棧進行撤銷和恢復的操作,棧頂部是當前的狀態。更多的,可以把相關狀態進行永續性儲存。
(3)提示程式碼:
publicvoidundo(){
if(!canUndo())return;
mementos.pop();
}
(4)提示關鍵字:redo,undo
16、TemplateMethod(模板方法)模式:旨在一個方法中實現一個演算法,並遵循演算法中某些步驟的定義,從而使得其他類可以重新定義這些新步驟。
(1)經典範例:(根據不同規則)排序
(2)個人理解:在演算法的實現中,把一些需要自定義的部分(通常是演算法的核心部分),留在外部的類來實現。並可以需要實現的部分設定鉤子。
(3)提示程式碼:
Array.sout(rockets,newApogeeComparator());
publicclassApogeeComparatorimplementsComparator{
//basemethodaboutsort……
}
(4)提示關鍵字:演算法框架+演算法步驟
17、State(狀態)模式:旨在將與狀態有關的處理邏輯分散到代表狀態的各個類中。
(1)個人理解:將所有的狀態都構建成一個相應的類,它們的超類對外部各個事件提供相應的同意介面,使得呼叫者無需判斷當前狀態。
(2)提示程式碼:
publicclassDoor2extendsObservable{
publicvoidtouch(){
state.touch();
}
}
publicclassDoorOpenextendsDoorState{
publicvoidtouch(){
door.setState(door.STAYOPEN);
}
}
(3)提示關鍵字:狀態處理分散
18、Strategy(策略)模式:旨在把可選的策略或方案封裝到不同的類中,並在這些類中實現一個共同的操作。
(1)個人理解:為不同的解決方案建立類,在執行的時候選擇一個策略執行。與State模式比較,兩者很接近,前者傾向在可選的方案中選擇,後者是在不同的狀態之間遷移。
(2)提示程式碼:
privateAdvisorgetAdvisor(){
if(advisor……null){
if(promotionAdvisor.hasItem())
advisor=promotionAdvisor;
//maybemoreelseif
}
returnadvisor;
}
(3)提示關鍵字:策略選擇+策略執行
19、Command(命令)模式:旨在將請求封裝為一個物件,並將該請求物件作為引數;客戶可以提供不同的請求物件,如佇列請求,時間請求或者日誌請求;也可以讓客戶準備呼叫該請求的特定上下文。
(1)經典範例:選單命令(actionPerformed())
(2)個人理解:將方法(一般是execute()方法)封裝在物件中,使用時直接呼叫相關mand物件的execute()方法即可。可以作為Template模式的替代模式。
(3)提示程式碼:
Commanddoze=newCommand(){
publicvoidexecute(){
//dosomething
}
}
publicclassCommandTimer{
Publicstaticlongtime(Commandmand){
mand.execute();
}
}
longactual=CommandTimer.time(doze);
(4)提示關鍵字:封裝物件
20、Interpreter(直譯器)模式:旨在使開發者可以根據自己定義的組合規則生成可執行的物件。
(1)個人理解:常與Command和Composite模式配合使用,對命令進行組合使用,有點像程式設計中使用語句構造功能。
(2)提示程式碼:
publicclassIfCommandextendsCommand{
protectedTermterm;
protectedCommandbody;
protectedCommandelseBody;
publicIfCommand(Termterm,Commandbody,CommandelseBody){
this.term=term;
this.body=body;
this.elseBody=elseBody;
}
publicvoidexecute(){
if(term.eval()!=null)
body.execyte();
else
elseBody.execute();
}
}
(3)提示關鍵字:直譯器,組合物件
21、Decorator(裝飾器)模式:旨在使開發者能夠動態地組織物件的行為。
(1)經典範例:流和輸出器,函式包裝器
(2)個人理解:在執行時動態建立不同的變化
(3)提示程式碼:
BufferedOutputStreamout=
newBufferedOutputStream(
newGZIPOutputStream(
newFileOutputStream(args[1])));
(4)提示關鍵字:動態組合
22、Iterator(迭代器)模式:旨在為開發人員提供一種順序訪問集合元素的方法。
(1)個人理解:在新建一個結構的時候,為順序訪問其元素,可以同樣新建一個對應的迭代器類。也可以自定義訪問元素的其他方式(比如逆序)。
(2)提示程式碼:
Listemployees;
ListIteratorforward(employees);
ReverseListIteratorbackward(employees);
PrintEmployees(forward);
PrintEmployees(backward);
(3)提示關鍵字:訪問元素
23、Visitor(訪問者)模式:旨在讓開發者能夠在不修改現有類層次結構的前提下擴充套件該類層次結構的行為。
(1)個人理解:在開發類的時候,留有一個accept()操作,該操作接受一個visitor引數。在需要為類增加新的操作時,無需改變原來的類層次,直接編輯visitor中的visit操作,然後使用accept()方法接受即可。
(2)提示程式碼:
publicclassFindVisitorimplementsMachineVisitor{
publicMachineComponetfind(MachineComponetmc){
mc.accept(this);
}
publicvoidvisit(MachineCompositemc){
//dosomething
}
}
MachineComponentfactory=OozinozFactory.dublin();
MachineComponentmachine=newFindVisitor().find(factory,3404);
(3)提示關鍵字:不改變類層次附錄:面向物件基礎
小A:“為什麼要‘面向物件’?”
大B:“面向物件方法使構建系統更容易,因為:解決正確的問題,正常工作,易維護,易擴充,易重用。大家發現面向物件更易理解,實現可以更簡單。把資料和功能組合在一起簡單而自然,分析和實現之間的概念跨度更小,設計良好的一組物件能彈性地適應重用和變化,視覺化模型提供更有效的溝通,建模過程有助於建立通用詞彙以及在開發者和使用者/客戶之間達成共識。非計算機程式設計人員也能理解物件模型,這些好處可以使用面向物件方法獲得,但面向物件方法不能保證這一點。”
小A:“怎樣才能變成優秀的面向物件設計者?”
大B:“只有靠經驗和聰明的頭腦才能做到。”
過程化方法(TheProceduralApproach)
小A:“怎樣過程化方法?”
大B:“系統由過程(procedures)組成,過程之間互相傳送資料,過程和資料各自獨立,集中於資料結構、演算法和運算步驟的先後順序,過程經常難以重用,缺乏具有較強表現力的視覺化建模技術,分析與實現之間需要進行概念轉換,本質上是機器/組合語言的抽象,從設計模型到程式碼實現跨度很大。”
面向物件方法
大B:“系統由物件組成,物件互相傳送訊息(過程呼叫)相關的資料和行為緊密地繫結在物件中,把問題領域建模成物件,要解決的問題自然的對映為程式碼的實現,可視模型表現力強,相對容易理解,集中於實現之前所確定的職責(responsibilities)和介面。強有力的概念:介面,抽象,封裝,繼承,委託(delegation)和多型。問題的可視模型逐漸進化成解決方案模型,設計模型與程式碼實現之間跨度較小努力縮減軟體的複雜度。”
溫度換算
大B:“下面我就以溫度換算為例。”
過程/函式化方法
floatc=getTemperature();//假定為攝氏度。
floatf=toFarenheitFromCelcius(c);
floatk=toKelvinFromCelcius(c);
floatx=toKelvinFromFarenheit(f);
floaty=toFarenheitFromKelvin(k);
面向物件方法
Temptemp=getTemperature();
floatc=temp.toCelcius();
floatf=temp.toFarenheit();
floatk=temp.toKelvin();
包含有資料的Temp的內部單元是什麼?
建模(Modeling)
小A:“成功的程式能解決真實世界的問題。”
大B:“嗯,是的。它們緊密對應於需要解決的問題。對問題領域和使用者活動進行建模。”
小A:“建模促進與使用者更好的視覺化交流。”
大B:“成功的面向物件設計總是一開始就由領域專家和軟體設計者建立一個反映問題領域的視覺化的‘物件模型’。”
小A:“嗯。是的。”
大B:“你願意讓承包人在沒有設計藍圖的情況下建造你的新房子嗎?”
小A:“那當然不行啦。”
物件
大B:“你知道怎樣去理解什麼是物件嗎?”
小A:“物件代表真實或抽象的事物,有一個名字,有明確的職責(well-definedresponsibilities),展示良好的行為(well-definedbehavior),介面清晰,並且儘可能簡單、自相容,內聚,完備(self-consistent,coherent,andplete)。”
大B:“嗯,對。(通常)不是很複雜或很大,只需要理解自己和一小部分其他物件的介面,與一小部分其它物件協同工作(teamplayers),儘可能地與其它物件鬆散耦合(looselycoupled),很好地文件化,以便他人使用或重用,物件是類的例項,每一個物件都有唯一的標識,類定義一組物件的介面和實現,即定義了這些物件的行為,抽象類不能擁有例項,只要有抽象類(如寵物),通常就會有能夠例項化的具體類(如貓,狗等),一些面嚮物件語言(如Smalltalk)支援元類(metaclass)的概念,程式設計師可以隨時(on-the-fly)定義一個類,然後例項化。這種情況下,類也是一個物件,即元類。物件一旦例項化,就不能更改它的類。”
物件的特徵
大B:“那你知道物件有什麼特徵嗎?”
小A:“有唯一標識,可以分成許多種類(即類),可以繼承或聚合。行為、職責明確,介面與實現分離,隱藏內部結構,有不同的狀態,可以提供服務,可以給其它物件傳送訊息,從其它物件接收訊息,並做出相應響應,可以把職責委託給其它物件。”
大B:“對,說得非常全面。”
類
小A:“怎麼樣才叫類呢?”
大B:“有公共的屬性和行為的一組物件可以抽象成為類,物件通常根據你所感興趣的屬性而分類。”
小A:“喔。”
大B:“例如:街道,馬路,高速公路……不同的程式對它們分類也不同。交通模擬器程式,單行道,雙通道,有分車道的,住宅區的,限制通行的維護排程程式,路面材料,重型卡車運輸類本身也可以有屬性和行為。例如:養老金管理程式中的‘僱員’類僱員總數,僱員編制多少,不同語言對類的支援略有不同:Smalltalk把類當作物件(很有好處),C++提供最小限度的支援(有時會帶來很多煩惱),Java位於上述兩者之間,類也是物件,類可以有屬性‘僱員’類可以有一個包含其所有例項的列表(list)‘彩票’類可以有一個種子(seed)用於產生隨機票號,該種子被所有例項共享,類可以有行為,僱員”類可以有getEmployeeBySerialNum行為。‘彩票’類可以有generateRandomNumber行為。
封裝
大B:“只暴露相關的細節,即公有介面(publicinterface)。”
小A:“封裝什麼?如何封裝?”
大B:“隱藏‘齒輪和控制桿’只暴露客戶需要的職責,防止物件受到外界干擾,防止其它物件依賴可能變化的細節,資訊隱藏有助於物件和模組之間的鬆散耦合,使得設計更加靈活,更易於重用,減少程式碼之間的依賴,‘有好籬笆才有好鄰居’。例如:汽車的氣動踏板。”
小A:“怎樣才能更好地實踐?”
大B:“最佳實踐:物件之間只通過方法(函式)互相訪問。切忌直接訪問屬性。”
classPerson{
publicintage;
}
classBetterPerson{
privateintage;//changetodateOfBirth
publicintgetAge(){returnage;}
}
更完善的Person類可能是:privatedateOfBirth
抽象
小A:“什麼是抽象?”
大B:“抽象使得泛化(generalizaions)成為可能,簡化問題-忽略複雜的細節,關注共性,並且允許變更,人類經常使用泛化。當你看見約翰和簡家裡的那頭灰德國牧羊犬時,你有沒有……想到‘狗’這個詞?抽象同樣能簡化計算機程式。例如,軟體中有兩個重要抽象:客戶端和伺服器(clientsandservers)。”
小A:“喔。”
大B:“在圖形使用者介面中,系統可能會詢問使用者各種問題:是或不是多選一?輸入數字,任意文字問題統一處理這些問題會顯得很簡單,每一個問題都作為Question類的特例(specialization);程式只需維護這些問題的例項列表,分別呼叫各自的askTheUser()方法。”
繼承
小A:“什麼是繼承?”
大B:“繼承用於描述一個類與其它類的不同之處。例如:類Y像類X,但有下列不同……”
小A:“為什麼使用繼承?”
大B:“你有兩種型別,其中一種是另一種的擴充套件。有時(不是所有時候)你想忽略物件之間的不同,而只關注它們的共同之處(基類)。這就是泛化。假如某系統需要對不同的形狀進行操作(經典例子):有時你並不關心你正在操作的形狀的種類(例如,移動形狀時)有時你必須知道形狀的種類(在顯示器上繪製形狀)”
小A:“怎樣去理解派生類?”
大B:“派生類繼承自基類;派生類擴充套件了基類;派生類是基類的特殊化(specialization)。派生類能夠提供額外的狀態(資料成員),或額外的行為(成員函式/方法),或覆蓋所繼承的方法。基類是所有它的派生類的泛化。如:通常所有寵物都有名字。基類(BaseClass)=父類(parentclass)=超類(superclass)派生類(DerivedClass)=子類(childclass)=子類(subclass)”
小A:“喔。”
大B:“繼承含有(有些,不是全部)是一個(is-a)或是一種(is-a-kind-of)的關係,正方形是一種矩形(使用繼承),Leroy是一種狗(不使用繼承),傳統的過程分析和設計中不能很好地模擬這種關係。繼承是一種強有力的機制,使我們關注共性,而不是特定的細節。使得程式碼可以重用且富有彈性(能適應變化)。”
小A:怎樣去實現繼承?
大B:“實現繼承(Implementationinheritance):派生類繼承基類的屬性和行為。”
小A:“又應該怎樣去介面繼承?”
大B:“介面繼承(Interfaceinheritance):類實現抽象介面的方法,保留既定語義(intendedsemantics)C++允許多重實現繼承。Java規定派生類只能有一個基類,但可以繼承自多個介面。”
多型
小A:“什麼是多型?”
大B:“多型是一種允許多個類針對同一訊息有不同的反應的能力。對於任何實現了給定介面的物件,在不明確指定類名的情況下,就可以使用。例如:question.askTheUser();當然,這些不同反應都有類似的本質儘可能使用介面繼承和動態(執行期)繫結Liskov替換原則:如果Y是X的子類,那麼在任何使用X例項的地方都可以用Y的例項來替換。”
演示多型的Java程式碼
//File:question/QuestionTest.java
//下面的程式碼將輸出什麼?
//RefertotheBeginningJavalinkonthecoursewebsite.
packagequestion;
abstractclassQuestion{//Fullclassnameisquestion.QuestionTest
publicQuestion(Stringtext){//Constructor
theText=text;
}
publicabstractvoidaskTheUser();
protectedStringtheText;
}
classYesNoQuestionextendsQuestion{
publicYesNoQuestion(Stringtext){super(text);}
publicvoidaskTheUser(){
System.out.println(theText);
System.out.println(“YESorNO……?”);
}
}
classFreeTextQuestionextendsQuestion{
publicFreeTextQuestion(Stringtext){super(text);}
publicvoidaskTheUser(){
System.out.println(theText);
System.out.println(“Well……?Whatstheanswer……?”);
}
}
publicclassQuestionTest{
publicstaticvoidmain(String[]args){
Question[]questions=getQuestions();
for(inti=0;i