设计模式(注重理解为什么),
設(shè)計模式
- 設(shè)計模式
- 定義
- 反模式
- 閱讀前須知
- 好處
- 設(shè)計模式分類:
- 分類意義
- 分類
- 常見疑問:
- 六大原則總結(jié):
- 別人總結(jié):
- 我的總結(jié):
- 單例模式,策略模式
- 單件模式的定義:
- 場景
- 為什么要使用單件模式:
- 為什么要只創(chuàng)建一個對象:
- 為什么使用單件模式不使用全局變量:
- 單例模式常見問題
- 策略模式(代碼復(fù)用)
- 場景
- 策略模式用到的原則:(為什么)
- 策略回顧遇到的問題
- 觀察者模式:
- 定義(發(fā)布與訂閱)
- 場景
- 原則:(為什么)
- 回顧時存在的疑問
- 裝飾者模式
- 定義
- 場景
- 原則
- 需注意的地方:
- 工廠模式
- 定義
- 場景
- 為什么
- 常見疑惑
- 抽象工廠模式
- 定義
- 場景
- 原則
- 工廠方法與抽象工廠的聯(lián)系和區(qū)別:
- 聯(lián)系:
- 作用:
- 區(qū)別
- 命令模式
- 定義
- 場景(為什么)
- 實現(xiàn)解耦(幫助理解)
- 常見疑問
- 適配器與外觀模式
- 適配器,裝飾者,外觀的作用(區(qū)別
- 定義
- 場景
- 常見疑問
- 對象適配器和類適配器
- 裝飾者和適配器
- 外觀模式
- 定義
- 原則
- 常見問題
- 外觀模式回顧存在疑問
- 模板方法模式(代碼復(fù)用)
- 定義
- 場景
- 策略模式和模板方法
- 原則
- 常見疑問
- 迭代器與組合模式
- 定義
- 常見疑問
- 場景
- 常見疑問
- 組合模式
- 定義
- 場景
- 常見疑問
- 幫助理解對答:
- 狀態(tài)模式
- 定義
- 場景
- 策略模式和狀態(tài)模式
- 常見疑問
- 代理模式
- 定義
- RMI (遠(yuǎn)程代理)
- 虛擬代理
- 相關(guān)場景:設(shè)置CD封面的虛擬代理
- 核心代碼:
- 常見疑問:
- 代理和裝飾者的圍爐夜話:
- 保護(hù)代理
- 相關(guān)類圖:
- 相關(guān)步驟:
- 核心代碼:
- 常見疑問
- 其他類型代理
- 復(fù)合模式
- 定義
- 一群模式攜手合作(不是復(fù)合)
- 常見疑問:
- 關(guān)于組合模式中的安全性和透明性:
- MVC
- MVC與Web
- 其他模式
- 橋接
- 生成器
- 責(zé)任鏈
- 蠅量
- 解釋器
- 中介者
- 備忘錄
- 原型
- 訪問者
本文章參考自headfirst設(shè)計模式,如有侵權(quán)請聯(lián)系作者
看完這本書心得:該書主要就是教如何設(shè)計出更有彈性的系統(tǒng)。沒想到花了十天才把這本書看完。這本書我覺得只要有面向?qū)ο蠡A(chǔ)都可以嘗試看看。可以長長見識,耗時不多。
最大作用:幫助理解源碼的含義,以及為何這么用。
設(shè)計模式
定義
模式實在某情境下,針對某問題的某種解決方案。
(模式必須應(yīng)用一個重復(fù)出現(xiàn)的問題,即可以多次使用,像什么破窗找東西就不實際)
反模式
定義:反模式告訴你如何采用一個不好的解決方案解決一個問題。
作用:1. 反模式告訴你為何這個解決方案從長遠(yuǎn)看會造成不好的影響。
2. 除了告訴你什么解決方案不好之外,也會向你建議一些會引向好的解決方案的可能性。閱讀前須知
開發(fā)的目標(biāo)就是簡單(簡單的方式行得通,滿足需求,就別用模式),過度使用設(shè)計模式可能導(dǎo)致代碼被過度工程化,模式會帶來復(fù)雜性(產(chǎn)生過多的類和對象),模式只是指導(dǎo)方針,讓設(shè)計變得更簡單和系統(tǒng)更具備彈性(殺雞焉用宰牛刀),
所有的設(shè)計都應(yīng)該盡量保持簡單,只有在需要實踐擴(kuò)展的地方,才值得使用復(fù)雜性模式
總結(jié):總是使用滿足需要的最簡單解決方案,不管它用不用模式,讓設(shè)計模式自然而然地出現(xiàn)在你的設(shè)計中,而不是為了使用而是用。并且牢記:你所遇到大多數(shù)的模式都是現(xiàn)有模式的變體,而非新模式。
好處
好處:
1.松耦合,建立彈性系統(tǒng)-》加快開發(fā)速度(因為松耦合,前后端可以同時進(jìn)行)->分工明確,前端只負(fù)責(zé)前端,后端只負(fù)責(zé)后端。
2.共享詞匯(一聽就明白)
我建立的這個廣播類,他會持續(xù)地追蹤所有傾聽他的對象,只要有數(shù)據(jù)進(jìn)來,就會把消息發(fā)送給每一個傾聽者。最棒的地方在于傾聽者可以在任何時候加入這個廣播類,也可以在任何時候從廣播中刪除。而這個廣播類本身并不用知道這些傾聽者的確切類,只要實現(xiàn)了正確的接口,就可以當(dāng)傾聽者。 —》只要說觀察者就行了,簡單清晰精確。
設(shè)計模式分類:
分類意義
怎么分類不重要,重要的是了解這些模式和它們之間的關(guān)系。分類,然后好比較可讓你對模式有清晰的概念
就比如汽車也分為:跑車,經(jīng)濟(jì)車,卡車……,一旦有了分類或類目,你就可以方便地這么說:“如果你想從硅谷開車到圣克魯斯,那么跑車將會是最好的選擇”。或者“因為石油的市場狀況日益惡化,所以應(yīng)該購買經(jīng)濟(jì)車,比較省油。
通過分類,我們可以將一組模式視為一個群體。當(dāng)我們需要一個創(chuàng)建型模式,但又不知掉確切是哪一個的時候,就可以用創(chuàng)建型模式這個詞來統(tǒng)稱它。
而且分類也有助于我們比較相同類目內(nèi)的其他成員。比方說:“迷你車是最具有風(fēng)格的小型車”。或者幫助我們縮小縮小范圍,”我需要一部省油的車子“。
再者說,對于改變對象接口來說,適配器模是最好的結(jié)構(gòu)型模式。
并且,類目還可以開發(fā)新的領(lǐng)域,比方說”我們真的想要開發(fā)一部跑車,具有法拉利的性能和Miata的價格“.(壽命短之類的缺陷)
所以類目可以讓我們思考模式群組之間的關(guān)系,以及同一組模式內(nèi)模式之間的關(guān)系,還可以讓我們找出新的模式。但是為什么只是用三個類目呢?
(就像夜晚天空中的星星一樣,你可以看見許多類目。“三”是一個適當(dāng)?shù)臄?shù)目,并且是有許多人所決定出來的數(shù)目,他有助于更好地進(jìn)行模式分類,但是的確有人建議用四個,五個或更多)
分類
結(jié)構(gòu)型:通過用接口將實現(xiàn)與抽象聯(lián)系起來的方式把已有對象組合起來進(jìn)行建模(把類或?qū)ο?strong>組合到更大的結(jié)構(gòu)中,[以獲取新的結(jié)構(gòu)或功能])。(外觀與適配器、代理模式、裝飾器模式、橋接模式、組合模式、享元模式)
行為型:(涉及類和對象如何交互以及分配職責(zé)。[對象之間的溝通與互連])通過對變化進(jìn)行封裝使得所建立的模型可以提供靈活的行為方式。(策略與觀察者、模板方法模式、迭代子模式、責(zé)任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式)
創(chuàng)建型:將對象實例化,這類模式都提供一個方法,將客戶從所需要實例化的對象解耦(單例模式、抽象工廠與工廠方法、建造者模式、原型模式)
常見疑問:
問:為何裝飾者模式被歸類為結(jié)構(gòu)類目中?不應(yīng)該是行為類目嗎?
答:是的,許多人都這么認(rèn)為,四人組(設(shè)計成這樣的四個人)的想法:結(jié)構(gòu)型模式用來描述類和對象如何被組合以建立新的結(jié)構(gòu)或新的功能。裝飾者模式允許你通過“將某對象包裝進(jìn)另一個對象的方式",來組合對象以提供新的功能.所以焦點是在于如何動態(tài)地組合對象以獲取功能,而不是行為型模式的目的——對象之間的溝通與互連。請牢記,這幾個模式的意圖并不相同,而這通常是了解某個模式屬于哪個類目的關(guān)鍵。
其實還有兩類:并發(fā)型模式和線程池模式,用一個圖片來整體描述一下:
轉(zhuǎn)至:
設(shè)計模式的三大分類
六大原則總結(jié):
別人總結(jié):
六大設(shè)計原則:
單一職責(zé)
開閉原則
里氏替換原則:
迪米特法則(即下面的最少知識原則):
只與你的直接朋友交談,不跟“陌生人”說話
其含義是:如果兩個軟件實體無須直接通信,那么就不應(yīng)當(dāng)發(fā)生直接的相互調(diào)用,可以通過第三方轉(zhuǎn)發(fā)該調(diào)用。其目的是降低類之間的耦合度,提高模塊的相對獨立性。
迪米特法則要求限制軟件實體之間通信的寬度和深度,正確使用迪米特法則將有以下兩個優(yōu)點。
- 降低了類之間的耦合度,提高了模塊的相對獨立性。
- 由于親合度降低,從而提高了類的可復(fù)用率和系統(tǒng)的擴(kuò)展性。
缺點:
過度使用迪米特法則會使系統(tǒng)產(chǎn)生大量的中介類,從而增加系統(tǒng)的復(fù)雜性,使模塊之間的通信效率降低。所以,在釆用迪米特法則時需要反復(fù)權(quán)衡,確保高內(nèi)聚和低耦合的同時,保證系統(tǒng)的結(jié)構(gòu)清晰。
從迪米特法則的定義和特點可知,它強(qiáng)調(diào)以下兩點:
- 從依賴者的角度來說,只依賴應(yīng)該依賴的對象。
- 從被依賴者的角度說,只暴露應(yīng)該暴露的方法。
接口隔離原則:
定義:
1、客戶端不應(yīng)該依賴它不需要的接口。
2、類間的依賴關(guān)系應(yīng)該建立在最小的接口上。
以上兩個定義的含義是:要為各個類建立它們需要的專用接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調(diào)用。
接口隔離原則和單一職責(zé)的區(qū)別:
接口隔離原則和單一職責(zé)都是為了提高類的內(nèi)聚性、降低它們之間的耦合性,體現(xiàn)了封裝的思想,但兩者是不同的:
- 單一職責(zé)原則注重的是職責(zé),而接口隔離原則注重的是對接口依賴的隔離。
- 單一職責(zé)原則主要是約束類,它針對的是程序中的實現(xiàn)和細(xì)節(jié);接口隔離原則主要約束接口,主要針對抽象和程序整體框架的構(gòu)建。
https://www.jianshu.com/p/3232c9891403
我的總結(jié):
抽象倒置原則
自己總結(jié):
所有的原則都應(yīng)該在有幫助的時候才遵守。所有的設(shè)計都不免需要折衷(在抽象和速度之間取舍,在空間和時間之間平衡……)。雖然原則提供了方針,但在采用原則之前,必須全盤考慮所有的因素。
松耦合(面向接口,增加復(fù)用潛力,依賴于抽象而不依賴于具體,降低對象之間的依賴,一個類應(yīng)當(dāng)只對應(yīng)一個職能,),來建立彈性的OO系統(tǒng),提高系統(tǒng)的可維護(hù)性。 松耦合-》易復(fù)用,易擴(kuò)展,還可以加快效率(易多人開發(fā))
易擴(kuò)展:在不修改現(xiàn)有代碼的情況下,可搭配新的行為。
常用到的設(shè)計原則:
//在設(shè)計模式中,所謂“實現(xiàn)一個接口”并“不一定”表示“寫一個類,并利用implement關(guān)鍵詞來實現(xiàn)某個java接口”。”實現(xiàn)一個接口“泛指“實現(xiàn)某個超類型(可以是類或者接口)的某個方法”。
多用組合,少用繼承 (java不支持多重繼承。使用類繼承難松耦合);
封裝變化。(狀態(tài),策略)
開放-關(guān)閉原則(對擴(kuò)展開放,對修改關(guān)閉,結(jié)合上面的原則)(裝飾者)
單一責(zé)任:一個類應(yīng)該只有一個引起變化的原因。
(內(nèi)聚:一個類或一個模塊被設(shè)計成只支持一組相關(guān)的功能)
當(dāng)我們允許一個類不但要完成自己的事情(管理某種內(nèi)聚),還同時要擔(dān)負(fù)更多的責(zé)任(例如遍歷)時,我們就給了這個類兩個變化的原因。即當(dāng)這個集合改變的話這個類也必須改變,遍歷的方式改變的話,這個類也必須改變。
最少知識原則(得墨忒耳法則):只和你的密友談話。(只和朋友交談) 外觀模式跟最少知識原則的關(guān)系
解釋:當(dāng)你正在設(shè)計一個系統(tǒng),不管是任何對象,你都要注意它所交互的類有哪些,并注意它和這些類是如何交互的。
這個原則希望我們在設(shè)計中,不要讓太多的類耦合在一起,免得修改系統(tǒng)中一部分,會影響到其他部分。
好萊塢原則:別調(diào)用(打電話給)我們,我們會調(diào)用(打電話給)你。
作用:**防止“依賴腐敗”**的方法。如:當(dāng)高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側(cè)組件,而邊側(cè)組件又依賴低層組件時,依賴腐敗就發(fā)生了。這樣的情況,是無法分清系統(tǒng)是如何設(shè)計的。
要依賴抽象,不要依賴具體類。(依賴倒置原則)(抽象工廠)
為交互對象之間的松耦合設(shè)計而努力。(觀察者)
單例模式,策略模式
單例懶漢式代碼
** DCL(double check lock)模式為什么要使用volatile,防止半初始化。 **
如T t = new T() ; 在匯編中有如下幾步:
當(dāng)jvm進(jìn)行優(yōu)化發(fā)生指令重排序(對于單線程不影響最后結(jié)果,代碼執(zhí)行順序會發(fā)生改變),即第3個指令會與第4個交換,多線程中會出現(xiàn)半初始化問題,一般情況在百萬數(shù)據(jù)情況下不會出現(xiàn),以上就可能出現(xiàn)。
單件模式的定義:
確保類只有一個實例,并提供一個全局訪問點。
場景
注冊表等只需要創(chuàng)建一個類的地方。
為什么要使用單件模式:
為什么要只創(chuàng)建一個對象:
避免程序的行為異常。比如注冊表,多了會導(dǎo)致配置紊亂。再者printer spooler,計算機(jī)可以使用多個打印機(jī),但當(dāng)存在兩個printer spooler就會出現(xiàn)打印錯誤,比如同時使用一個打印機(jī)則打印出錯。
為什么使用單件模式不使用全局變量:
節(jié)省資源,延遲實例化,全局變量在一開始就要求實例化。
再者無法確保只創(chuàng)建一個實例 如:慎用全局變量,再則全局變量也會變相鼓勵開發(fā)人員,用許多全局變量指向許多小對象來造成命名空間的污染。單件不鼓勵這樣的現(xiàn)象,但單件仍然可能被濫用
。
單例模式常見問題
策略模式(代碼復(fù)用)
描述了怎樣按需要在一組可替換的算法中選用算法,幾把所定義的一些算法各自封裝起來,可根據(jù)客戶的需要分別使用它們。該模式可使算法獨立變化而不影響他的客戶。該模式既注重算法使用的靈活性,又注重對需求的變化性。
場景
需要擴(kuò)展類但又不想全部繼承其方法如鴨子類,不同類型鴨子有不同的quack和fly方式。
策略模式用到的原則:(為什么)
(解決了繼承復(fù)用代碼功能,但不需要全部繼承,就是因為針對變化的部分)
策略回顧遇到的問題
1.怎么實現(xiàn)的?(將經(jīng)常變化的地方封裝[拿出來作為接口],在讓其他類實現(xiàn)[方便擴(kuò)展],然后再在Context類中組合進(jìn)去【即添加變化類的接口】,在調(diào)用即可)
//比如duck類中的fly經(jīng)常需要修改或者擴(kuò)展。,就把fly封裝起來,讓接口去實現(xiàn)。 flyBehavior //Context類 public abstract class Duck{FlyBehavior flyBehavior;public Duck(){}public void peformFly(){flyBehavior.fly();//fly行為由被封裝為flyBehavior}public void setFlyBehavior(FlyBehavior flyBehavior){ this.flyBehavior = flyBehavior;} //也可以放到構(gòu)造器中,不過就不夠動態(tài) } //ContextConcrete類 public class ModelDuck extends Duck{FlyBehavior flyBehavior;public ModelDUck(){flyBehavior = new FlyNoWay();// fly:I can't fly;} } //封裝變化為接口 public interface FlyBehavior{void fly(); } //接口實現(xiàn) public class FlyNoWay implements FlyBehavior {public void fly(){System.out.println("I can't fly");} } public class FLyRocketPowered implements FLyBehavor{public void fly(){System.out.println("I'm flying with a rocket");} }//Test public class Test{public static void main(String args[]){Duck model = new ModelDuck();model.performFly(); //i can't fly.model.setFlyBehavior(new FlyRocketPowered());model.performFly(); // i'm flying with a rocket.} }觀察者模式:
定義(發(fā)布與訂閱)
用于定義對象間的一對多的依賴關(guān)系,當(dāng)一個對象發(fā)生變化時,所有依賴他的對象都將得到通知并自動更新。
以下是兩種形式:建議使用第一種方式(subject)。
第二種方式(Observable)違背了針對接口編程以及面向組合而非繼承的原則。但java內(nèi)置支持。
場景
出版(subject(接口)/observable(類))與訂閱(observer)
報紙出版者和訂閱者的關(guān)系。
原則:(為什么)
為了交互對象之間的松耦合設(shè)計而努力。
回顧時存在的疑問
怎么拉而不是自動強(qiáng)行推送??
在update方法的形參改為Subject/Observable,將Subject的變量用get公開獲取。(這也是拉的缺點,數(shù)據(jù)被公開獲取)
對比推/拉代碼即可觀察出,
推送是將所有參數(shù)發(fā)過去,update中接受所有參數(shù)。
拉是傳遞一個Subject/Observable對象,并且要讓其參數(shù)變量可以公開獲取
拉:
class CurrentCondition implements Observer,DisplayElement{ void update(Subject subject,Object args){//WeatherDate entends Subjectif(sub instanceOf WeatherData){WeatherData weatherData = (WeatherData) sub;//通過get獲取相關(guān)參數(shù)this.temperature = weatherData.getTemperature()j;//...this.humidity = weatherData.getHumidity(); display();//就是一個輸出獲取參數(shù)值的作用} }}推:
class CurrentCondition implements Observer,DisplayElement{ void update(double temperature,double humidity,double pressure){//通過推送過來的值進(jìn)行賦值this.temperature = weatherData.getTemperature();//...this.humidity = weatherData.getHumidity(); display();//就是一個輸出獲取參數(shù)值的作用} }}class WeatherData implements Subject{private ArrayList observers;private double temperature;//...//...others needs parameterspubic WeatherData(){observers = new ArrayList();}void notifyObservers(){//可以加些限制條件 改變值大于多少才會通知,在setMeasurements方法中設(shè)置即可。for (Observer obs:observers){obs = observers.get(i);obs.update(temperature,...) //推送直接將所有參數(shù)發(fā)送過去}} }裝飾者模式
定義
裝飾者模式動態(tài)地將責(zé)任附加到對象上。若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。
// Beverage beverage = new Milk(new Milk(new Espresso())); 可以用無數(shù)個裝飾者類包裝組件
具體實現(xiàn):
場景
星巴克Beverage(飲料)進(jìn)行擴(kuò)展。 (類數(shù)量太多,價格變動)
原則
類應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。(開放-關(guān)閉原則)
缺點:引入新的抽象層次,增加代碼的復(fù)雜度,。
需注意的地方:
1.難以理解 2.類型問題 3.因為組合原因會增加大量小對象導(dǎo)致程序很復(fù)雜。 以及包裝過多。
工廠模式
定義
? 工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
(將實例化放到一個抽象方法中,當(dāng)需要創(chuàng)建時,才調(diào)用create,子類實現(xiàn)create)(原本是直接根據(jù)參數(shù)進(jìn)行if判斷)(但其實感覺差不多,就是添加種類時,可以到具體子類中修改)
理解:工廠方法讓子類決定要實例化的類是哪一個。容易理解錯誤-》所謂的“決定”,并不是指模式允許子類本身在運行時做決定,而是指在編寫創(chuàng)建者類時,不需要知道實際創(chuàng)建的產(chǎn)品時哪一個。選擇了使用哪個子類,自然就決定了實際創(chuàng)建的產(chǎn)品時什么。
(比如:pizzastore時抽象類,不知道是選擇CaliforniaPizzaStore子類還是NewYorkPizzaStore子類中的那種類型pizza(cheese,pepperoni)。
為什么使用工廠模式?
將創(chuàng)建對象代碼集中起來,方便維護(hù),將客戶代碼和真實的實現(xiàn)解耦。(針對接口實現(xiàn))
場景
2.簡單工廠不是一種設(shè)計模式,更像一種編程習(xí)慣。(經(jīng)常被用到) (就是將需要new的地方法全部放在SimpleFactory種,讓它來create()創(chuàng)建。
1.需要new的地方,將其用放到抽象方法中讓子類去實現(xiàn),不同子類實現(xiàn)不同特點的new 產(chǎn)品(加盟店)。
為什么
為什么在工廠模式代碼中仍要new?對象創(chuàng)建是現(xiàn)實的,如果不創(chuàng)建任何對象,就無法創(chuàng)建java程序。
1.將創(chuàng)建對象代碼集中起來,方便維護(hù),將客戶代碼和真實的實現(xiàn)解耦。(針對接口實現(xiàn))(通過將創(chuàng)建代碼放入抽象方法中,讓子類實現(xiàn)create)
常見疑惑
抽象工廠模式
定義
抽象工廠模式提供一個接口,用于創(chuàng)建相關(guān)或依賴對象的家族,而不需要明確指定具體類。
場景
披薩配料工廠。
原則
要依賴抽象,不要依賴具體類。(依賴倒置原則)
要滿足該原則應(yīng)盡量(有足夠理由時,可以違反。比如一個不太會改變的類,直接實例化具體類也沒什么大礙,比如String)以下三點:
1.變量不可以持有具體類的引用(使用工廠)
2.不要讓類派生自具體類 (派生具體類,就會依賴于具體類。盡量派生自一個抽象[接口或抽象類])
3.不要覆蓋基類中已實現(xiàn)的方法。 (如果覆蓋基類已實現(xiàn)的方法,那么你的基類就不是一個真正適合被繼承的抽象。基類中已實現(xiàn)的方法,應(yīng)該由所有子類共享)
不使用工廠模式時,pizzastore依賴于任何一個具體的pizza類,每新增一個種類,就多一個依賴。
工廠方法與抽象工廠的聯(lián)系和區(qū)別:
聯(lián)系:
抽象工廠的方法經(jīng)常以工廠方法的方式實現(xiàn)。
抽象工廠的任務(wù)是定義一個負(fù)責(zé)創(chuàng)建一組產(chǎn)品的接口。這個接口內(nèi)的每個方法都創(chuàng)建一個具體產(chǎn)品,同時我們利用實現(xiàn)抽象工廠的子類來提供這些具體的做法。所以,在抽象工廠中利用工廠方法實現(xiàn)生產(chǎn)方法是相當(dāng)自然的做法。
簡單工廠,雖然不是真正的設(shè)計模式,但仍不失為一個簡單的方法,可以將客戶程序從具體類解耦。
作用:
這兩種模式以及簡單工廠都可以將對象的創(chuàng)建封裝起來,從而減少應(yīng)用程序和具體類之間的依賴,以便于得到更松耦合、更有彈性的設(shè)計。
區(qū)別
工廠方法使用繼承:把對象的創(chuàng)建委托給子類,子類實現(xiàn)工廠方法來創(chuàng)建對象。
抽象工廠使用對象組合:對象的創(chuàng)建被是現(xiàn)在工廠接口所暴露出來的方法中。
內(nèi)容為按照headfirst 設(shè)計模式.順序?qū)W習(xí)(除非特意學(xué)習(xí)某個模式)
命令模式
定義
命令模式將“請求"封裝成對象,以便使用不同的請求、隊列或者日志來參數(shù)化其他對象。命令模式也支持可撤銷的操作。
場景(為什么)
當(dāng)需要“發(fā)出請求的對象”和“接受與執(zhí)行這些請求的對象”解耦時使用命令模式
1.接待員(接受訂單)與廚師:接待員無需知道訂單內(nèi)容的含義
2.遙控器與電燈打開(或者其他功能):遙控器無需知道按鈕的實現(xiàn)(意義)。直接按就行。(將遙控器和電燈對象解耦)
從而不用不用寫出這樣的代碼:
? if(slot1 = Light) then light.on(),
? else if(slot1 ==Hottub) then hottob.jetsOn(),
? else if… //這樣的代碼每次添加新的物品都必需修改代碼,這會造成潛在的錯誤,而且工作沒完沒了。(為什么)
3.日志恢復(fù)(通過命令模式記錄保存之前的操作,在電腦死機(jī)時可以成批依次地調(diào)用命令進(jìn)行恢復(fù))以及事務(wù)系統(tǒng)(原子性,全做或全不做)
實現(xiàn)解耦(幫助理解)
遙控器和電燈(功能對象)完全解耦,即使再新的廠商對象也無需在RemoteControl中修改代碼(NullCommand可以用作空對象[不實現(xiàn)功能的按鈕],用于做擴(kuò)展)
常見疑問
1.如何實現(xiàn)多次撤銷?
使用棧保存之前的命令。每次撤銷都是彈出最上面的命令(最后的一次操作)。
適配器與外觀模式
適配器,裝飾者,外觀的作用(區(qū)別
適配器將一個對象包裝起來以改變接口;
裝飾者將一個對象包裝起來以增加新的行為和責(zé)任;
外觀將一群對象”包裝“起來以簡化其接口。
定義
適配器模式將一個類的接口,轉(zhuǎn)換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間
場景
TurkeyAdapter:讓火雞能使用鴨子的方法。
插座適配,將兩口和適配器配合變成三口(類似轉(zhuǎn)接器的作用)
還可以用于兼容適配,比如 之前用枚舉器,現(xiàn)在要用新的迭代器,但是也希望以前的代碼也使用迭代器這時候就可以使用適配器適配兼容。
常見疑問
一個適配器需要做多少“適配”的工作?如果我需要實現(xiàn)一個很大的目標(biāo)接口,似乎有“很多”工作要做?
的確如此。實現(xiàn)一個適配器所需要進(jìn)行的工作,的確和目標(biāo)接口的大小成正比。如果不用適配器,就必須改寫客戶端的代碼來調(diào)用這個新的接口,將會花費許多力氣來做大量的調(diào)查工作和代碼改寫工作。
一個適配器只能夠封裝一個類?
適配器模式的工作是將一個接口轉(zhuǎn)換成另一個。雖然大多數(shù)的適配器模式所采取的例子都是讓一個適配器包裝一個被適配者。但我們知道這個世界其實復(fù)雜多了,所以你可能遇到一些狀況,需要讓一個適配器包裝多個被適配者。這涉及另一個模式——外觀模式。人們常常將外觀模式和適配器模式混為一談。
萬一我的系統(tǒng)中新舊并存,那應(yīng)該保存哪個?還是不用適配器更好?
可以創(chuàng)建一個雙向適配器,支持兩邊的接口。 (實現(xiàn)涉及的兩個接口即可)
對象適配器和類適配器
裝飾者和適配器
外觀模式
定義
外觀模式提供了一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口。外觀定義了一個高層接口,讓子系統(tǒng)更容易使用。
原則
最少知識原則(得墨忒耳法則):只和你的密友談話。
解釋:當(dāng)你正在設(shè)計一個系統(tǒng),不管是任何對象,你都要注意它所交互的類有哪些,并注意它和這些類是如何交互的。
這個原則希望我們在設(shè)計中,不要讓太多的類耦合在一起,免得修改系統(tǒng)中一部分,會影響到其他部分。
如何遵循此原則?
只應(yīng)該調(diào)用屬于以下范圍的方法:
采用最少知識原則有什么缺點嗎?
是的,雖然這個原則減少了對象之間的依賴,研究顯示這會減少軟件的維護(hù)成本;但是采用這個原則也會導(dǎo)致更多的”包裝“類被制造出來,以處理和其他組件的溝通,這可能會導(dǎo)致復(fù)雜度和開發(fā)時間的增加,并降低運行時的性能。
常見問題
如果外觀封裝了子系統(tǒng)的類,那么需要底層功能的客戶如何接觸這些類?
外觀沒有”封裝“子系統(tǒng)的類,只是提供簡化的接口。所以客戶如果覺得有必要,依然可以直接使用子系統(tǒng)的類。即外觀模式可以在提供簡化接口的同時,依然將系統(tǒng)完整的功能暴露出來,以供有需要的人使用。
一個子系統(tǒng)可以有多個外觀。
除了能夠提供一個比較簡單的接口之外,外觀模式還有其他優(yōu)點嗎?
外觀模式允許你將客戶實現(xiàn)從任何子系統(tǒng)中解耦。比方說:你得到了大筆加薪,所以想要升級你的家庭影院,采用全新的和以前不一樣接口的組件。如果當(dāng)初你的客戶代碼是針對外觀而不是針對子系統(tǒng)編寫的,現(xiàn)在就不需要改變客戶代碼,只需要修改外觀代碼(而且有可能廠商會提供新版的外觀代碼)。
我可不可以這樣說,適配器模式和外觀模式的差異在于:適配器包裝一個類,而外觀可以代表許多類?
不對!提醒你,適配器模式將一個或多個類接口變成客戶所期望的一個接口。雖然大多數(shù)教科書所采用的例子中適配器只適配一個類,但是你可以適配許多類來提供一個接口讓客戶編碼。類似地,一個外觀也可以只針對一個擁有復(fù)雜接口地類提供簡化的接口。兩種模式的差異,不在于他們”包裝"了及各類,而在于他們的意圖。適配器模式的意圖是,”改變“接口來符合客戶的期望;而外觀模式的意圖是,提供子系統(tǒng)的一個簡化接口。
外觀模式回顧存在疑問
1.外觀模式是怎么只提供一個接口的?最少知識原則又跟它有什么關(guān)系?
模板方法模式(代碼復(fù)用)
定義
模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
(即定義一個算法步驟,讓子類為一個或多個步驟提供實現(xiàn))
場景
咖啡和茶的制作方式相似,則可以將相同地方提取出來,不同的地方抽象,泛化名稱,放到咖啡因飲料(基類)的一個制作方法中。
數(shù)組的sort方法也是模板方法。 需要子類實現(xiàn)Comparable中的CompareTo
為什么說數(shù)組的sort也算是一個模板方法?
這個模式的重點在于提供一個算法,并讓子類實現(xiàn)某些步驟而數(shù)組的排序做法很明顯地并非如此!但是我們都知道,荒野中地模式并非總是如同教科書例子一般地中規(guī)中矩,為了符合當(dāng)前地環(huán)境和實現(xiàn)地約束,他們總是要被適當(dāng)?shù)匦薷摹_@個Array類sort()方法的設(shè)計者受到一些約束。通常我們無法設(shè)計一個類繼承Java數(shù)組,而sort()方法下能夠適用于所有的數(shù)組(每個數(shù)組都是不同的類).所以他們定義了一個靜態(tài)方法,而由被排序的對象內(nèi)的每個元素自行提供比較大小的算法部分。所以,這雖然不是教科書上的模板方法,但它的實現(xiàn)仍然符合模板方法的精神。再者,由于不需要繼承數(shù)組就可以使用這個算法,這樣使得排序變得更有彈性、更有用。
策略模式和模板方法
排序?qū)崿F(xiàn)實際上看起來更像是策略模式,而不是模板方法模式,為什么歸為模板方法模式?
你之所以會這么認(rèn)位,可能是因為策略模式使用對象組合,在某種程度上,你是對的——我們使用數(shù)組對象排序我們的數(shù)組,這部分和策略模式非常相似。但是請記住,在策略模式中你所組合的類實現(xiàn)了整個算法。數(shù)組所實現(xiàn)的排序算法并不完整,它需要一個類填補(bǔ)compareTo()方法的實現(xiàn)。因此我們認(rèn)為這更像模板方法。
兩者對話:
| 我定義一個算法家族,并讓這些算法可以互換。正因為每一個算法都被封裝起來了,所以客戶可以輕易使用地不同的算法。 | |
| 嘿!聽起來好像是我在做的事情。但是我的意圖和你不一樣:我的工作是要定義一個算法的大綱,而由我的子類定義其中某些步驟的內(nèi)容。這么一來,我在算法中的個別步驟可以由不同的實現(xiàn),但是算法的結(jié)構(gòu)維持不變。而你似乎必須要放棄對算法的控制。 | |
| 我不確定話可以這么說……更何況,我并不是使用繼承進(jìn)行算法的實現(xiàn),我是通過對象組合的方式,讓客戶可以選擇算法實現(xiàn)。 | |
| 這我記得。但是我對算法有更多的控制權(quán),而且不會重復(fù)代碼,事實上,除了極少一部分之外,我的算法的每一個部都是相同的,所以我的類比你的有效率得多。會重復(fù)使用到的代碼,都被我放進(jìn)了超類中,好讓所有的子類共享。 | |
| 你或許更有效率一點(只是一點點),也的確需要更少的對象。和我所采用的委托模型比起來,你也沒有那么復(fù)雜。但是因為我使用對象組合,所以我更有彈性。利用我,客戶就可以在運行時改變他們的算法,而客戶所需要做的,只是改用不同的策略對象罷了。拜托作者選擇把我擺在第一章,這不是沒有道理的! | |
| 好吧,我真替你感到高興,但是你別忘了,環(huán)顧四周,我可是最常被使用的模式。為什么呢?因為我在超類中提供了一個基礎(chǔ)的方法,達(dá)到代碼的復(fù)用,并允許子類指定行為。我相信你會看到這一點在創(chuàng)建框架時是非常棒的! | |
| 也許呢……但是,別忘了依賴!你的依賴程度比我高。 | |
| 這話怎么說?我的超類時抽象的。 | |
| 但是你必須依賴超類中的方法的實現(xiàn),因為這是你算法中的一部分。但我就不同了,我不依賴任何人;整個算法我自己搞定吧! |
其他例子:java.io的InputStream類有一個read()方法,是由子類實現(xiàn)的,而這個方法又會被read(byte b[],int off,int len)模板方法使用。
原則
好萊塢原則:別調(diào)用(打電話給)我們,我們會調(diào)用(打電話給)你。
作用:**防止“依賴腐敗”**的方法。如:當(dāng)高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側(cè)組件,而邊側(cè)組件又依賴低層組件時,依賴腐敗就發(fā)生了。這樣的情況,是無法分清系統(tǒng)是如何設(shè)計的。
做法:低層組件可以參與計算,低層組件不可以直接調(diào)用高層組件,高層組件控制何時以及如何讓低層組件參與。
好萊塢原則和依賴倒置原則之間的關(guān)系如何?
依賴倒置原則教我們盡量避免使用具體類,而多使用抽象。而好萊塢原則是用在創(chuàng)建框架或組件上的一種技巧,好讓低層組件能夠被掛鉤進(jìn)計算中,而且又不會讓高層組件依賴低層組件。**兩者的目標(biāo)都在于解耦,但是依賴倒置原則更加注意如何在設(shè)計中避免依賴,好萊塢原則則教我們一個技巧,創(chuàng)建一個有彈性的設(shè)計,允許低層結(jié)構(gòu)能夠互相操作,而又防止其他類太過依賴他們。
低層組件不可以調(diào)用高層組件的方法嗎?
并不盡然。事實上,低層組件在結(jié)束時,常常會調(diào)用從超類中繼承來的方法。我們所要做的是,避免讓高層和低層組件之間有明顯的環(huán)狀依賴
常見疑問
什么時候使用抽象方法,什么時候使用鉤子(hook)?
當(dāng)子類必須提供某個方法或步驟的實現(xiàn)時,使用抽象方法。如果算法中的某個部分是可選的,那么這個部分可以使用鉤子,子類可以實現(xiàn)這個鉤子,也可以選擇不實現(xiàn)。
使用鉤子的真正目的?
1)讓子類實現(xiàn)算法中可選的部分。(或者在鉤子對于子類的實現(xiàn)并不重要的時候)
2)讓子類能夠有機(jī)會對模板方法中即將發(fā)生的(或剛剛發(fā)生的)步驟作出反應(yīng)。比如說,名為justReOrderedList()的鉤子方法允許子類在內(nèi)部列表重新組織后執(zhí)行某些動作(例如在屏幕上重新顯示數(shù)據(jù))。鉤子也可以讓子類有能力為其抽象類做一些決定。
子類必須實現(xiàn)抽象類中所有的方法。
似乎我應(yīng)該保持抽象方法的數(shù)目越少越好,否則,在子類中實現(xiàn)這些方法將會很麻煩?
可以讓算法內(nèi)的步驟不要切割的太細(xì)。但是如果步驟太少的話,會比較沒有彈性,要自己判斷折衷,也請記住,某些步驟是可選的,所有你可以將這些步驟是現(xiàn)成鉤子,而不是實現(xiàn)成抽象方法,這樣就可以讓抽象類的子類減輕負(fù)擔(dān)。
迭代器與組合模式
定義
描述:提供一個方式來遍歷集合,而無需暴露集合的實現(xiàn)
迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,但又不暴露其內(nèi)部的表示。
(將調(diào)用者和集合操作【遍歷】解耦)
常見疑問
1.讓方法不生效:throw new lang.UnsupportedOperationException。
2.在多線程的情況下,可能會由多個迭代器引用同一個對象集合。remove()會造成怎樣的影響?
后果并沒有指明,所以很難預(yù)料。當(dāng)你的程序在多線程的代碼中使用到迭代器時,必須特別小心。
場景
在使用ArrayList和數(shù)組的情況下的集合代碼都可以通過迭代器來循環(huán)不需要寫兩遍循環(huán)代碼,且實現(xiàn)其接口還可以隱藏內(nèi)部表示。
常見疑問
我聽說過“**內(nèi)部的”迭代器和“外部的”的迭代器。**這是什么?我們在前面例子中實現(xiàn)的是哪一種?
我們實現(xiàn)的是外部的迭代器,也就是說,客戶通過調(diào)用next()取得下一個元素。而內(nèi)部的迭代器則是由迭代器自己控制。你必須將操作傳入給迭代器比外部迭代器更沒有彈性。然而,某些人可能認(rèn)為內(nèi)部的迭代器比較容易使用,因為只需將操作告訴它,它就會幫你做完所有事情。
對于散列表這樣的集合,元素之間并沒有明顯的次序關(guān)系,我們該怎么辦?
迭代器意味著沒有次序(java.util.Enumeration是一個有次序的迭代器實現(xiàn))。只是取出所有的元素,并不表示取出元素的先后就代表元素的大小次序。對于迭代器來說,數(shù)據(jù)結(jié)構(gòu)可以是有次序的,或者是沒有次序的,甚至數(shù)據(jù)可以是重復(fù)的。除非某個集合的文件有特別說明,否則不可以對迭代器所取出的元素大小順序做出假設(shè)。
組合模式
定義
描述:客戶可以將對象和集合以及個別對象一視同仁
組合模式允許你將對象組合成樹形結(jié)構(gòu)來表現(xiàn)“整體/部分”層次結(jié)構(gòu)。組合能讓客戶以一致的方式處理個別對象以及對象組合。
場景
在使用ArrayList和數(shù)組的情況下的集合代碼都可以通過迭代器來循環(huán)不需要寫兩遍循環(huán)代碼,且實現(xiàn)其接口還可以隱藏內(nèi)部表示。在這樣的組合下,再在這數(shù)組或ArrayList中添加子數(shù)組或者子ArrayList。
常見疑問
組件、組合、樹?我被搞混了?。
組合包含組件。組件有兩種:組合和葉節(jié)點元素。聽起來像遞歸是不是?組合有一群孩子,這些孩子可以時別的組合或者葉節(jié)點元素。當(dāng)你用這種方式組織數(shù)據(jù)的時候,最終會得到樹形結(jié)構(gòu)(正確的說法是由上而下的樹形結(jié)構(gòu)),根部是一個組合,而組合的分支逐漸往下延伸,直到葉節(jié)點為止。
組合跟迭代器又有什么關(guān)系?
合作無間。
代碼舉例:
我:到底怎么回事?首先說“一個類一個責(zé)任”,現(xiàn)在卻給我們一個讓一個類有兩個責(zé)任的模式。組合模式不但要管理層次結(jié)構(gòu),而且還要執(zhí)行菜單的操作。
設(shè)計者:你的觀察有幾分真實性。我們可以這么說,組合模式以單一責(zé)任設(shè)計原則換取透明性。什么是透明性?通過讓組件的接口同時包含一些管理子節(jié)點和葉節(jié)點的操作,客戶就可以將組合和葉節(jié)點一視同仁。(無法統(tǒng)一處理菜單和菜單項就會失去透明性)即看不見-》不可見。
也就是說,一個元素究竟是組合還是葉節(jié)點,對客戶是透明的。 現(xiàn)在我們在MenuComponent類中同時具有兩種類型的操作(例如試圖把菜單添加到菜單項),所以我們失去了一些“安全性"這是設(shè)計上的抉擇;我們當(dāng)然也可以采用另一種方向的設(shè)計,**將責(zé)任區(qū)分開來放在不同的接口中。**這么一來,設(shè)計上就比較安全,但我們也因此失去了透明性,客戶的代碼將必須用條件語句和instanceof操作符處理不同類型的節(jié)點。 所以,回到你的問題,這是一個很典型的折衷案例。==盡管我們受到設(shè)計原則的指導(dǎo),但是,我們總是需要觀察某原則對我們的設(shè)計所造成的影響。有時候,我們會故意做一些看似違反原則的事情。==然而,在某些例子中,這是觀點的問題;比方說,讓管理孩子的操作(例如add(),remove(),getChild())出現(xiàn)在葉節(jié)點中,似乎很不恰當(dāng),但是換個角度看,你可以把葉節(jié)點視為沒有孩子的節(jié)點。
幫助理解對答:
狀態(tài)模式
定義
定義:狀態(tài)模式允許對象在內(nèi)部狀態(tài)改變時改變它的行為,對象看起來好像修改了他的類.
解釋:==狀態(tài)模式將狀態(tài)封裝成為獨立的類,并將動作委托到代表當(dāng)前狀態(tài)的對象,我們知道行為會隨著內(nèi)部狀態(tài)而改變。比如糖果機(jī),在NoQuarterState或HasQuarterState兩種狀態(tài)時,投入25分錢會有不同的行為。==一個對象“看起來好像修改了它的類”是什么意思呢?從客戶視角來看:如果說你使用的對象能夠完全改變它的行為,那么你會覺得這個對象實際上是從別的類實例化而來的。然而,實際上,你知道我們是在使用組合通過簡單引用不同的狀態(tài)對象來造成類改變的假象。
描述:封裝基于狀態(tài)的行為,并將行為委托到當(dāng)前狀態(tài)(對象)。
封裝:將每個狀態(tài)封裝進(jìn)一個類-》改變局部化(不用到大量if中改變)
場景
糖果機(jī)根據(jù)相應(yīng)行為進(jìn)入相應(yīng)的狀態(tài)。
不使用狀態(tài)模式:(要擴(kuò)展時混亂):
使用狀態(tài)模式:
解決的問題:
策略模式和狀態(tài)模式
基本常識:策略模式和狀態(tài)模式是雙胞胎,在出生時才分開。你已經(jīng)知道了,策略模式是圍繞可以互換的算法來創(chuàng)建業(yè)務(wù)的。狀態(tài)走的是更崇高的路,狀態(tài)模式通過改變對象內(nèi)部的狀態(tài)來幫助對象控制自己的行為。
策略模式和狀態(tài)模式有相同的類圖,但他們的意圖不同。
狀態(tài):
策略:
區(qū)別:
策略模式通常會用行為或算法來配置Context類。狀態(tài)模式允許Context隨著狀態(tài)改變而改變行為。
策略:有一個可以實例化的類,而且通常給它一個實現(xiàn)某些行為的策略對象。
狀態(tài): Context對象會隨著時間而改變狀態(tài),而任何的狀態(tài)改變都是定義好的。
常見疑問
在GumballMachine中,狀態(tài)決定了下一個狀態(tài)應(yīng)該是什么。ConcreteState總是決定接下來的狀態(tài)是什么嗎?
不,并非總是如此,Context也可以決定狀態(tài)轉(zhuǎn)換的流向。一般來說,當(dāng)狀態(tài)轉(zhuǎn)換是固定的時候,就適合放在Context;然而,當(dāng)轉(zhuǎn)換是更動態(tài)的時候,通常會放在狀態(tài)類中(例如,在GumballMachine中,由運行時糖果的數(shù)目決定狀態(tài)要轉(zhuǎn)換倒NoQuarter還是SoldOut)。將狀態(tài)轉(zhuǎn)換放在狀態(tài)類中的缺點是:狀態(tài)類之間產(chǎn)生了依賴。在我們的GumballMachine實現(xiàn)中,我們試圖通過使用Context上的getter方法把依賴減到最小,而不是顯示硬編碼具體狀態(tài)類。請注意,在做這個決策的同時,也等于是在為另一件事情做決策:當(dāng)系統(tǒng)進(jìn)化時,究竟哪個類是對修改封閉(Context還是狀態(tài)類)的。
客戶會直接和狀態(tài)交互嗎?
不會。狀態(tài)是用在Context中來代表它的內(nèi)部狀態(tài)以及行為的,所以只有Context才會對狀態(tài)提出請求。客戶不會直接改變Context的狀態(tài)。全盤了解狀態(tài)是Context的工作,客戶根本不了解,所以不會直接和狀態(tài)聯(lián)系。
如果在我的程序中Context有許多實例,這些實例之間可以共享狀態(tài)對象嗎?
是的,絕對可以,事實上這是很常見的做法。但唯一的前提是,你的狀態(tài)對象不能持有它們自己的內(nèi)部狀態(tài);否則就不能共享。想要共享狀態(tài),你需要把每個狀態(tài)都指定到靜態(tài)的實例變量中。如果你的狀態(tài)需要利用到Context中的的方法或者實例變量,你還必須在每個handler()方法內(nèi)傳入一個context的引用。
使用狀態(tài)模式似乎總是增加我們設(shè)計中類的數(shù)目。請看GumbleMachine的例子,新版本比舊版本多出了許多類!
沒錯。**在個別的狀態(tài)類中封裝狀態(tài)行為,結(jié)果總是增加這個設(shè)計中類的數(shù)目。這就是為了要獲取彈性而付出的代價。**除非你的代碼是一次性的,可以用完就扔掉(是呀!才怪!),那么其實狀態(tài)模式的設(shè)計是絕對值得的。其實真正重要的是你暴露給客戶的類數(shù)目,而且我們有辦法將這些額外的狀態(tài)類全都隱藏起來。
讓我們看一下另一種做法:如果你有一個應(yīng)用,他有很多狀態(tài),但是你決定不將這些狀態(tài)封裝在不同的對象中,那么你就會得到巨大的、整塊的條件語句。這會讓你的代碼不容易維護(hù)和理解。通過使用許多對象,你可以讓狀態(tài)變得很干凈,在以后理解和維護(hù)它們時,就可以省下很多的工夫。
狀態(tài)模式類圖顯示State是一個抽象類,但你不是使用接口實現(xiàn)糖果機(jī)狀態(tài)的嗎?
是的。如果我們沒有共同的功能可以放進(jìn)抽象類中,就會使用接口。在你實現(xiàn)狀態(tài)模式時,很可能想使用抽象類。這么一來,當(dāng)你以后需要在抽象類中加入新的方法時就很容易,不需要打破具體狀態(tài)的實現(xiàn)。
我們?yōu)槭裁葱枰猈innerState?為什么不直接在SoldState中發(fā)放兩顆糖果?
這個一個好問題。這兩個狀態(tài)幾乎一樣,唯一的差別在于,WinnerState狀態(tài)會放兩顆糖果。你當(dāng)然可以將發(fā)放兩顆糖果的代碼在SoldState中,當(dāng)然這么做有缺點,因為你等于是將兩個狀態(tài)用一個狀態(tài)類來代表。這樣做你犧牲了狀態(tài)類的清晰易懂來減少一些冗余代碼。你也應(yīng)該考慮到在前面的章節(jié)中所學(xué)到原則:一個類,一個責(zé)任。將WinnerState狀態(tài)的責(zé)任放進(jìn)SoldState狀態(tài)中,你等于是讓SoldState狀態(tài)具有兩個責(zé)任。那么促銷結(jié)束之后或者贏家的幾率改變之后,你又該怎么辦呢?所以,這必須用你的智慧來做折衷。
代理模式
定義
代理模式為另一個對象提供一個替身或占位符以控制對這個對象的訪問。
描述:包裝另一個對象,并控制對它的訪問。
相關(guān)類圖:
RMI (遠(yuǎn)程代理)
步驟:
1.制作一個遠(yuǎn)程接口Remote(MyRemote implements Remote),再添加客戶需要調(diào)用的方法
2.制作遠(yuǎn)程接口的實現(xiàn)。(MyRemoteImpl Extends UnicastRemoteObject implements MyRemote)
? 然后再通過注冊服務(wù){(diào)
? try{ MyRemote service = new MyRemoteImpl();
? Naming.rebind(“RemoteHello”,service);} catch(Exception ex){…}
3.產(chǎn)生Stub類和Skeleton類。 cmd: %rmic MyRemoteImpl
4.執(zhí)行remiregistry。 開啟注冊表用于給客戶端尋找相應(yīng)的stub類,然后stub類將請求發(fā)送給Skeleton,然后Skeleton打包(序列化)將相應(yīng)方法的返回值返回,stub在解包(反序列化)將結(jié)果返回給客戶端。
ps:對于RMI,程序員最常犯的三個錯誤:
1)忘了啟動遠(yuǎn)程服務(wù)之前先啟動rmiregistry(要用Naming.rebind()注冊服務(wù),rmiregistry必須是運行的)
2)忘了讓變量和返回值的類型成為可序列化的類型(這種錯誤無法在編譯器發(fā)現(xiàn),只會在運行時發(fā)現(xiàn))。
3)忘了給客戶提供stub類。
ps:關(guān)于客戶如何取得stub類?
我聽說,在Java 5 ,甚至連stub都不需要產(chǎn)生了,這是真的嗎?
? 是真的。Java 5的RMI和動態(tài)代理搭配使用,動態(tài)代理動態(tài)產(chǎn)生stub,遠(yuǎn)程對象的stub是java.lang.reflect.Proxy實例(連同一個調(diào)用處理器),它是自動產(chǎn)生的,來處理所有把客戶的本地調(diào)用變成遠(yuǎn)程調(diào)用的細(xì)節(jié)。所以,你不再需要使用rmic,客戶和遠(yuǎn)程對象溝通的一切都在幕后處理掉了。
5.啟動服務(wù) %java MyRemoteImpl
從哪里啟動?可能是從你的遠(yuǎn)程實現(xiàn)類中的main()方法,也可能是從一個獨立的啟動類。在這個簡單的例子中,我們是從實現(xiàn)類中的main()方法啟動的,先實例化一個服務(wù)對象,然后到RMI registry中注冊(Naming.rebind())。
虛擬代理
虛擬代理控制訪問創(chuàng)建開銷大的資源。
大概步驟:當(dāng)對象在創(chuàng)建前和創(chuàng)建中時,由虛擬代理來扮演對象的替身。對象創(chuàng)建后,代理就會將請求直接委托給對象。(可以防止程序被掛起)
相關(guān)場景:設(shè)置CD封面的虛擬代理
核心代碼:
常見疑問:
看起來,遠(yuǎn)程服務(wù)器和虛擬服務(wù)器差異非常大,它們真的是一個模式嗎?
在真實的世界里,代理模式有許多變體,這些變體都有共通點:都會將客戶對主題施加的方法調(diào)用攔截下來。
代理將客戶從ImageIcon解耦了,如果它們之間沒有解耦,客戶就必須等到每幅圖像都被取回,然后才能把它繪制在界面上。代理控制ImageIcon的訪問,以便在圖像完全創(chuàng)建之前提供屏幕上的代表。一旦ImageIcon被創(chuàng)建,代理就允許訪問ImageIcon。
我們要如何讓客戶使用代理,而不是真正的對象?
好問題。一個常用的技巧是提供一個工廠,實例化并返回主題。因為這是在工廠方法內(nèi)發(fā)生的,我們可以用代理包裝主題在返回,而客戶不知道也不在乎他使用的代理還是真東西。
我已經(jīng)知道代理和裝飾者的關(guān)系了,但是適配器?代理和適配器也很類似。
代理和適配器都是擋在其他對象的前面,并負(fù)責(zé)將請求轉(zhuǎn)發(fā)給他們。適配器會改變對象適配的接口,而代理則實現(xiàn)相同的接口。有一個額外相似性牽涉到保護(hù)代理。保護(hù)代理可以根據(jù)客戶的角色來決定是否允許客戶訪問特定的方法。所以保護(hù)代理可能只提供給客戶部分接口,這就和適配器很相像了(適配器是只能訪問共同接口的方法)。
代理和裝飾者的圍爐夜話:
保護(hù)代理
相關(guān)類圖:
相關(guān)步驟:
核心代碼:
創(chuàng)建InvocationHandler,invoke的實現(xiàn)代碼:
常見疑問
到底“動態(tài)代理”動態(tài)在哪里?是不是指在運行時才將它實例化并和handler聯(lián)系起來?
不是的。動態(tài)代理之所以被稱為動態(tài),是因為運行時才將它的類創(chuàng)建出來。代碼開始執(zhí)行時,還沒有proxy類,它是根據(jù)需要從你傳入的接口集創(chuàng)建的。
InvocationHandler看起來像一個很奇怪的proxy。它實現(xiàn)所代理的類的任何方法。?
這是因為InvocationHandler根本就不是proxy,它只是一個幫助proxy的類,proxy本身是利用靜態(tài)的Proxy.newProxyInstance()方法在運行時動態(tài)地創(chuàng)建地。
可以通過isProxyClass()的返回值true判斷一個類是動態(tài)代理類。
為什么使用skeleton?我以為我們早在java1.2就已經(jīng)擺脫了skeleton了。
確實,我們不需要真的產(chǎn)生skeleton,因為java1.2RMI利用reflectionAPI直接將客戶調(diào)用分派給原稱為服務(wù)。盡管如此,我們還是希望呈現(xiàn)skeleton,因為這可以幫助你從概念上理解內(nèi)部的機(jī)制。
其他類型代理
防火墻代理(Firewall Proxy):控制網(wǎng)絡(luò)資源的訪問,保護(hù)主題免于“壞客戶”的侵害。
智能引用代理(Smart Reference Proxy):當(dāng)主題被引用時,進(jìn)行額外的動作,例如計算一個對象被引用的次數(shù)。
緩存代理(Caching Proxy):為開銷大的運算結(jié)果提供暫時存儲:它也允許多個客戶共享結(jié)果,以減少計算或網(wǎng)絡(luò)延遲。(Web服務(wù)器代理,內(nèi)容管理與出版系統(tǒng))
同步代理(Synchronization Proxy):在多線程的情況下為主題提供安全的訪問。(出現(xiàn)在JavaSpaces,為分散式環(huán)境內(nèi)的潛在對象集合提供同步訪問控制)
復(fù)雜隱藏代理(Complexity Hiding Proxy):用來隱藏一個類的復(fù)雜集合復(fù)雜度,并進(jìn)行訪問控制。有時候也稱為外觀代理。復(fù)雜隱藏代理和外觀模式是不一樣的,因為代理控制訪問,而外觀模式只提供另一組接口。
寫入時復(fù)制代理(CopyOnWrite Proxy):用來控制對象的復(fù)制,方法是延遲對象的復(fù)制,知道客戶真的需要為止。這是虛擬代理的變體。(CopyOnWriteArrayList)
如有侵權(quán)請聯(lián)系本網(wǎng)站!
復(fù)合模式
定義
復(fù)合模式結(jié)合兩個或以上的模式,組成一個解決方案,解決一再發(fā)生的一般性問題。
(一般是多個模式解決一個問題,而不是一個模式解決一個問題然后加起來。)
一群模式攜手合作(不是復(fù)合)
鴨子 →鵝\xrightarrow{鵝}鵝?適配器→計算呱呱叫的次數(shù),不修改原有代碼\xrightarrow{計算呱呱叫的次數(shù),不修改原有代碼}計算呱呱叫的次數(shù),不修改原有代碼?裝飾者→每次創(chuàng)建都要包裝\xrightarrow{每次創(chuàng)建都要包裝}每次創(chuàng)建都要包裝? 使用抽象工廠將創(chuàng)建和包裝放在一個方法中→管理一群對象\xrightarrow{管理一群對象}管理一群對象?迭代器和組合模式→持續(xù)觀察某些對象\xrightarrow{持續(xù)觀察某些對象}持續(xù)觀察某些對象?觀察者模式
常見疑問:
–相關(guān)代碼定義:
適配器:(適配鵝)
public class Goose{public void hook(){System.out.println("Honk");//咯咯叫} } public class GooseApater implements Quackable{Goose goose;public GooseAdapter(Goose goose){this.goose = goose;}public void quack(){goose.honk();} } 實現(xiàn):new GooseAdapter(new Goose());裝飾者:(動態(tài)添加新的行為——添加呱呱叫計數(shù)器,而不需要修改源代碼)
public class QuackCount implements Quackable{Quackable duck;static int numberOfQuacks;public QuackCounter(Quackable duck){this.duck = duck;}public void quack(){duck.quack;numberOfQuacks++;}public static int getQuacks(){return numberOfQuacks;} } 實現(xiàn):new QuackCounter(new MallardDuck());抽象工廠:將創(chuàng)建和裝飾部分包裝起來集中管理
public abstract class AbstractDuck Factory{public abstract Quackable createMallardDuck();public abstract Quackable createRedHeadDuck();public abstract Quackable createrDuckCall();public abstract Quackable createRubberDuck(); } //創(chuàng)建沒有裝飾者的鴨子的工廠 public class DuckFactory extends AbstractDuckFactory{public Quackable createMallardDuck(){return new MallradDuck();}public Quackable createRedHeadDuck(){return new RedHeadDuck();}public Quackable createDuckCall(){return new DuckCall();}public Quackable createRubberDuck(){return new RubberDuck();} } //創(chuàng)建裝飾過的鴨子的工廠 public class CountingDuckFactory extends AbstractDuckFactory{public Quackable createMallardDuck(){return new QuackCounter(MallradDuck());}public Quackable createRedHeadDuck(){return new QuackCounter(RedHeadDuck());}public Quackable createDuckCall(){return new QuackCounter(DuckCall());}public Quackable createRubberDuck(){return new QuackCounter(RubberDuck());} }實現(xiàn):AbstractDuckFactory duckFactory = new CountingDuckFactory();duckFactory.createMallardDuck();...其中比較難想到的方式代碼:
迭代器和組合模式:以一致的方式來對待鴨群和單只小鴨
public class Flock implements Quackable{ //Flock就是鴨群,處理它也可以像單只鴨子一樣調(diào)用quack方法。ArrayList quackers = new ArrayList(); //葉節(jié)點就是Quackable。public void add(Quackable quacker){quackers.add(quacker);}public void quack()//flock也是Quackable,所以也要具備quack方法{Iterator iterator = quackers.iterator();while(iterator.haxNext()){Quackable quacker = (Quackable)iterator.next();quacker.quack();}} }關(guān)于組合模式中的安全性和透明性:
觀察者模式:(追蹤某只鴨子什么時候叫)
MVC
MVC與Web
其他模式
橋接
生成器
責(zé)任鏈
蠅量
解釋器
中介者
備忘錄
原型
訪問者
完結(jié)!恭喜你閱讀完常用基礎(chǔ)的設(shè)計模式。
總結(jié)
以上是生活随笔為你收集整理的设计模式(注重理解为什么),的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python stdin什么意思_pyt
- 下一篇: jdk8官方下载路径