设计模式相关
設計模式的七大原則
1. 單一職責原則
- 核心思想:任何一個軟件模塊中,應該有且只有一個被修改的原因。
- 栗子描述:Java程序猿非常喜歡把各種雜七雜八的功能性函數(shù)放到一個CommonService類里面,打 log 啊,各種查詢啊,鑒權啊。你要修改這個類的時候原因是不是很多?log 樣式要改,查詢條件要改,鑒權要加等等。咳咳,這就違反了單一職責原則啦。
怎么修改呢? 就是一個類只做好一類事情啊,如下圖。這樣你改任何一個類就只有一個理由了。比如你要改日志類,理由就是你要修改日志相關的功能。
總結一下單一職責原則:
- 優(yōu)點:職責越單一,被修改的原因就越少,類的復雜度降低、可讀性提高、可維護性提高、擴展性提高、降低了變更引起的風險。
- 缺點:如果類一味追求單一職責,有時會造成類的大爆炸。。。。。。。不過接口和方法肯定要遵循這個原則。
- 注意:?單一職責原則提出了一個編寫程序的標準,用“職責”或“變化原因”來衡量接口或類設計得是否優(yōu)良,但是“職責”和“變化原因”都是不可以度量的,因項目和環(huán)境而異。
2. 依賴倒置原則
- 核心思想:高層模塊不應該依賴底層模塊,二者都該依賴其抽象;抽象不應該依賴細節(jié);細節(jié)應該依賴抽象;
- 說明:高層模塊就是調用端,低層模塊就是具體實現(xiàn)類。抽象就是指接口或抽象類。細節(jié)就是實現(xiàn)類。
- 通俗來講:依賴倒置原則的本質就是通過抽象(接口或抽象類)使個各類或模塊的實現(xiàn)彼此獨立,互不影響,實現(xiàn)模塊間的松耦合。模塊之間交互應該依賴抽象,而非實現(xiàn)。
- 優(yōu)點:大幅提高了可擴展性,降低耦合度,使代碼層次更加清晰。
client 直接調用具體實現(xiàn)類。模塊之間交互應該依賴抽象,而非實現(xiàn)。這里違背了依賴倒置原則,但在業(yè)務最初的時候不會有任何問題。
隨著業(yè)務的增加,需要加上非關系型數(shù)據(jù)庫NoSQL,還對接了些政府業(yè)務,對方用Oracle。
這樣做的問題很明顯,應用代碼強耦合了實現(xiàn)類,當需要切換新的數(shù)據(jù)源實現(xiàn)的話,就不得不扒開原來的應用代碼做修改了?出了bug是不是要背鍋了?Leader可能要給你送飛機票了。
如果按照依賴倒置原則呢? 定義接口,讓不同的實現(xiàn)類去實現(xiàn)這個接口。我們發(fā)現(xiàn)通過接口,實現(xiàn)了依賴倒置。業(yè)務代碼不再依賴實現(xiàn)類,而是依賴接口,而同時,實現(xiàn)類也依賴接口。模塊之間交互從依賴實現(xiàn)到依賴抽象。
如果要切換數(shù)據(jù)庫,改一行代碼直接搞定不香嗎?
DbService dbService = new MysqlService(); //改成NoSqlService, OracleService即可還比較迷茫的童鞋可以移步《Java為什么要面向接口編程?》
3. 開閉原則
- 核心思想:軟件實體應該「對擴展開放,對修改關閉」。
- 說明:對擴展開放,意味著有新的需求或變化時,可以對現(xiàn)有代碼進行擴展以適應新的情況。對修改關閉,意味著類的設計一旦完成,就可以獨立完成工作,而不要對其進行任何修改。
還是上文數(shù)據(jù)庫接口的例子,當你需要新增數(shù)據(jù)庫時,原來的MySQL實現(xiàn)類無需改動,直接新增Oracle實現(xiàn)類和NoSQL實現(xiàn)類即可。也就是「對擴展開放,對修改關閉」。
4.接口隔離原則
- 核心思想:多個特定的接口要好于一個寬泛用途的接口
- 通俗來講:建立單一接口,不要建立龐大臃腫的接口,盡量細化接口,接口中的方法盡量少。也就是說,我們要為各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。
類A通過接口interface依賴類B,類C通過接口interface依賴類D,如果接口interface對于類A和類B來說不是最小接口,則類B和類D必須去實現(xiàn)他們不需要的方法。
我們按照ISP原則改上一版。比如A不需要用到方法4,方法5。就可以選擇不依賴他們。
總結一下接口隔離原則:
- 優(yōu)點:將外部依賴減到最少。你只需要依賴你需要的東西。這樣可以降低模塊之間的耦合。
- 注意:
- 接口盡量小,但是要有限度。對接口進行細化可以提高程序設計靈活性,但是如果過小,則會造成接口數(shù)量過多,使設計復雜化。所以一定要適度。
- 提高內聚,減少對外交互。使接口用最少的方法去完成最多的事
5. 迪米特法則(Law of Demeter)
- 核心思想:一個實體應當盡量少地與其他實體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對獨立
法則強調了以下兩點:
- 第一: 從被依賴者的角度來說:只暴露應該暴露的方法或者屬性
- 第二: 從依賴者的角度來說:只依賴應該依賴的對象
針對第一點,舉個栗子。當我們對計算機進行關機的時候,會先執(zhí)行一些列的動作:比如保存當前未完成的任務,然后是關閉相關的服務,接著是關閉顯示器,最后是關閉電源,這一系列的操作以此完成后,計算機才會正式被關閉。
如下代碼違背了迪米特原則(只暴露應該暴露的方法或者屬性)。我們看到close()方法已經封裝好了所有關機流程,其他方法沒有必要是public。只有close()方法才可以被其他調用computer的類可見。
針對第二點,舉個栗子。Sheldon和學霸Wang是好基友。某天Sheldon想認識個妹子,而學霸Wang和妹子認識。Sheldon如果直接去撩妹子,妹子會認為你是個渣男直接拒絕。所以Sheldon只能通過學霸Wang,由學霸Wang去傳遞信息給妹子(只依賴應該依賴的對象)。
6. 里氏替換原則
- 核心思想:程序中的父類型都應該可以正確地被子類替換。
- 原理:LSP 是繼承復用的基石,只有當子類可以替換掉父類,且軟件的功能不受到任何影響時,父類才能真正被復用,而子類也能夠在父類的基礎上增加新的行為。
- 通俗來講:只要有父類出現(xiàn)的地方,都可以使用子類來替代。而且不會出現(xiàn)任何錯誤或者異常。但是反過來卻不行。子類出現(xiàn)的地方,不能使用父類來替代。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據(jù)此斷定我喜歡動物,因為我并不喜歡老鼠,雖然它也是動物。
- 優(yōu)點:代碼共享,減少創(chuàng)建類的工作量。提高代碼的重用性,可擴展性。
- 缺點:繼承是侵入性的。只要繼承,就必須擁有父類的所有屬性和方法;增強了耦合性。當父類的常量、變量和方法被修改時,需要考慮子類的修改
- 注意:如果子類不能完整地實現(xiàn)父類的方法,或者父類的某些方法在子類中已經發(fā)生“畸變”,則建議斷開父子繼承關系 采用依賴、聚合、組合等關系代替繼承。
- 最佳實踐:我們最好將父類定義為抽象類,并定義抽象方法,讓子類重新定義這些方法,當父類是抽象類時候,父類不能實例化
7.合成復用原則? ??
1) 找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代
碼混在一起。
2) 針對接口編程,而不是針對實現(xiàn)編程。
3) 為了交互對象之間的松耦合設計而努力
總結
- 單一職責原則:提高代碼實現(xiàn)層的內聚度,降低實現(xiàn)單元彼此之間的耦合度
- 開閉原則:提高代碼實現(xiàn)層的可擴展性,提高面臨改變的可適應性,降低修改代碼的冗余度
- 里氏替換原則:提高代碼抽象層的可維護性,提高實現(xiàn)層代碼與抽象層的一致性
- 接口隔離原則:提高代碼抽象層的內聚度,降低代碼實現(xiàn)層與抽象層的耦合度,降低代碼實現(xiàn)層的冗余度
- 依賴倒置原則:降低代碼實現(xiàn)層由依賴關系產生的耦合度,提高代碼實現(xiàn)層的可測試性
- 迪米特法則:一個對象應該對其他對象保持最少的了解。盡量降低類與類之間的耦合。
- 合成復用原則:盡量使用合成/聚合的方式,而不是使用繼承
23種設計模式:23 種設計模式詳解(全23種)_雨中深巷的油紙傘的博客-CSDN博客_設計模式
注意事項:上面這篇文章有錯誤
總體來說設計模式分為三大類:
創(chuàng)建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾者模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代器模式、責任鏈模式、命令模式、備忘錄模式、狀態(tài)模式、訪問者模式、中介者模式、解釋器模式。
A、創(chuàng)建模式(5種)
工廠方法模式、抽象工廠模式、單例模式、建造者模式(生成器模式)、原型模式。
1 工廠模式
鏈接:https://www.zhihu.com/question/27125796/answer/1615074467
來源:知乎
在我們平常創(chuàng)建對象的時候,都是通過關鍵字 new 來實現(xiàn)的,例:Class A = new A() 。
在一些情況下,要創(chuàng)建的對象需要一系列復雜的初始化操作,比如查配置文件、查數(shù)據(jù)庫表、初始化成員對象等,如果把這些邏輯放在構造函數(shù)中,會極大影響代碼的可讀性。不妨定義一個類來專門負責對象的創(chuàng)建,這樣的類就是工廠類,這種做法就是工廠模式,在任何需要生成復雜對象的地方,都可以使用工廠模式。
工廠模式包括:簡單工廠(不在23種設計模式中)、工廠方法和抽象工廠。
下面我們詳細嘮嗑下這幾類的用法和區(qū)別。
解決的問題
客戶端在調用時不想判斷來實例化哪一個類或者實例化的過程過于復雜。
在工廠模式中,具體的實現(xiàn)類創(chuàng)建過程對客戶端是透明的,客戶端不決定具體實例化哪一個類,而是交由“工廠”來實例化。
簡單工廠
? 結構
定義一個創(chuàng)建對象的接口,讓其子類自己決定實例化哪一個工廠類。
- 抽象類或接口:定義了要創(chuàng)建的產品對象的接口。
- 具體實現(xiàn):具有統(tǒng)一父類的具體類型的產品。
- 產品工廠:負責創(chuàng)建產品對象。工廠模式同樣體現(xiàn)了開閉原則,將“創(chuàng)建具體的產品實現(xiàn)類”這部分變化的代碼從不變化的代碼“使用產品”中分離出來,之后想要新增產品時,只需要擴展工廠的實現(xiàn)即可。
? 使用
創(chuàng)建不同品牌的鍵盤
public interface Keyboard {void print();void input(Context context); }class HPKeyboard implements Keyboard {@Overridepublic void print() {//...輸出邏輯;}@Overridepublic void input(Context context) {//...輸入邏輯;}}class DellKeyboard implements Keyboard {@Overridepublic void print() {//...輸出邏輯;}@Overridepublic void input(Context context) {//...輸入邏輯;}}class LenovoKeyboard implements Keyboard {@Overridepublic void print() {//...輸出邏輯;}@Overridepublic void input(Context context) {//...輸入邏輯;}}/*** 工廠*/ public class KeyboardFactory {public Keyboard getInstance(int brand) {if(BrandEnum.HP.getCode() == brand){return new HPKeyboard();} else if(BrandEnum.LENOVO.getCode() == brand){return new LenovoKeyboard();} else if(BrandEnum.DELL.getCode() == brand){return new DellKeyboard();}return null;}public static void main(String[] args) {KeyboardFactory keyboardFactory = new KeyboardFactory();Keyboard lenovoKeyboard = KeyboardFactory.getInstance(BrandEnum.LENOVO.getCode());//...}}? 缺陷
上面的工廠實現(xiàn)是一個具體的類KeyboardFactory,而非接口或者抽象類,getInstance()方法利用if-else創(chuàng)建并返回具體的鍵盤實例,如果增加新的鍵盤子類,鍵盤工廠的創(chuàng)建方法中就要增加新的if-else。這種做法擴展性差,違背了開閉原則,也影響了可讀性。所以,這種方式使用在業(yè)務較簡單,工廠類不會經常更改的情況。
工廠方法
為了解決上面提到的"增加if-else"的問題,可以為每一個鍵盤子類建立一個對應的工廠子類,這些工廠子類實現(xiàn)同一個抽象工廠接口。這樣,創(chuàng)建不同品牌的鍵盤,只需要實現(xiàn)不同的工廠子類。當有新品牌加入時,新建具體工廠繼承抽象工廠,而不用修改任何一個類。
? 結構
- 抽象工廠:聲明了工廠方法的接口。
- 具體產品工廠:實現(xiàn)工廠方法的接口,負責創(chuàng)建產品對象。
- 產品抽象類或接口:定義工廠方法所創(chuàng)建的產品對象的接口。
- 具體產品實現(xiàn):具有統(tǒng)一父類的具體類型的產品。
? 使用
public interface IKeyboardFactory {Keyboard getInstance(); }public class HPKeyboardFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance(){return new HPKeyboard();} }public class LenovoFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance(){return new LenovoKeyboard();} }public class DellKeyboardFactory implements IKeyboardFactory {@Overridepublic Keyboard getInstance(){return new DellKeyboard();} }? 缺點
每一種品牌對應一個工廠子類,在創(chuàng)建具體鍵盤對象時,實例化不同的工廠子類。但是,如果業(yè)務涉及的子類越來越多,難道每一個子類都要對應一個工廠類嗎?這樣會使得系統(tǒng)中類的個數(shù)成倍增加,增加了代碼的復雜度。
抽象工廠
為了縮減工廠實現(xiàn)子類的數(shù)量,不必給每一個產品分配一個工廠類,可以將產品進行分組,每組中的不同產品由同一個工廠類的不同方法來創(chuàng)建。
例如,鍵盤、主機這2種產品可以分到同一個分組——電腦,而不同品牌的電腦由不同的制造商工廠來創(chuàng)建。
類似這種把產品類分組,組內不同產品由同一工廠類的不同方法實現(xiàn)的設計模式,就是抽象工廠模式。
抽象工廠適用于以下情況:
1. 一個系統(tǒng)要獨立于它的產品的創(chuàng)建、組合和表示時;
2. 一個系統(tǒng)要由多個產品系列中的一個來配置時;
3. 要強調一系列相關的產品對象的設計以便進行聯(lián)合使用時;
4. 當你提供一個產品類庫,而只想顯示它們的接口而不是實現(xiàn)時;
? 結構
- 抽象工廠:聲明了創(chuàng)建抽象產品對象的操作接口。
- 具體產品工廠:實現(xiàn)了抽象工廠的接口,負責創(chuàng)建產品對象。
- 產品抽象類或接口:定義一類產品對象的接口。
- 具體產品實現(xiàn):定義一個將被相應具體工廠創(chuàng)建的產品對象。
? 使用
public interface Keyboard {void print(); } public class DellKeyboard implements Keyboard {@Overridepublic void print() {//...dell...dell;} } public class HPKeyboard implements Keyboard {@Overridepublic void print() {//...HP...HP;} } public interface Monitor {void play(); } public class DellMonitor implements Monitor {@Overridepublic void play() {//...dell...dell;} } public class HPMonitor implements Monitor {@Overridepublic void play() {//...HP...HP;} } public interface MainFrame {void run(); } public class DellMainFrame implements MainFrame {@Overridepublic void run() {//...dell...dell;} } public class HPMainFrame implements MainFrame {@Overridepublic void run() {//...HP...HP;} } //工廠類。工廠分為Dell工廠和HP工廠,各自負責品牌內產品的創(chuàng)建 public interface IFactory {MainFrame createMainFrame();Monitor createMainFrame();Keyboard createKeyboard(); } public class DellFactory implements IFactory {@Overridepublic MainFrame createMainFrame(){MainFrame mainFrame = new DellMainFrame();//...造一個Dell主機;return mainFrame;}@Overridepublic Monitor createMonitor(){Monitor monitor = new DellMonitor();//...造一個Dell顯示器;return monitor;}@Overridepublic Keyboard createKeyboard(){Keyboard keyboard = new DellKeyboard();//...造一個Dell鍵盤;return Keyboard;} } public class HPFactory implements IFactory {@Overridepublic MainFrame createMainFrame(){MainFrame mainFrame = new HPMainFrame();//...造一個HP主機;return mainFrame;}@Overridepublic Monitor createMonitor(){Monitor monitor = new HPMonitor();//...造一個HP顯示器;return monitor;}@Overridepublic Keyboard createKeyboard(){Keyboard keyboard = new HPKeyboard();//...造一個HP鍵盤;return Keyboard;} } //客戶端代碼。實例化不同的工廠子類,可以通過不同的創(chuàng)建方法創(chuàng)建不同的產品 public class Main {public static void main(String[] args) {IFactory dellFactory = new DellFactory();IFactory HPFactory = new HPFactory();//創(chuàng)建戴爾鍵盤Keyboard dellKeyboard = dellFactory.createKeyboard();//...} }? 優(yōu)缺點
增加分組非常簡單,例如要增加Lenovo分組,只需創(chuàng)建Lenovo工廠和具體的產品實現(xiàn)類。分組中的產品擴展非常困難,要增加一個鼠標Mouse,既要創(chuàng)建抽象的Mouse接口, 又要增加具體的實現(xiàn):DellMouse、HPMouse, 還要再每個Factory中定義創(chuàng)建鼠標的方法實現(xiàn)。
? 總結
- 簡單工廠:唯一工廠類,一個產品抽象類,工廠類的創(chuàng)建方法依據(jù)入參判斷并創(chuàng)建具體產品對象。
- 工廠方法:多個工廠類,一個產品抽象類,利用多態(tài)創(chuàng)建不同的產品對象,避免了大量的if-else判斷。
- 抽象工廠:多個工廠類,多個產品抽象類,產品子類分組,同一個工廠實現(xiàn)類創(chuàng)建同組中的不同產品,減少了工廠子類的數(shù)量。
在下述情況下可以考慮使用工廠模式:
總之,工廠模式就是為了方便創(chuàng)建同一接口定義的具有復雜參數(shù)和初始化步驟的不同對象。工廠模式一般用來創(chuàng)建復雜對象。只需用new就可以創(chuàng)建成功的簡單對象,無需使用工廠模式,否則會增加系統(tǒng)的復雜度。
此外,如果對象的參數(shù)是不固定的,推薦使用Builder模式。
后記
在實際項目中,結合Spring中的InitializingBean接口,可以利用@Autowired注解優(yōu)雅的實現(xiàn)工廠。
2 單例模式
定義:確保一個類最多只有一個實例,并提供一個全局訪問點
單例模式可以分為兩種:預加載和懶加載
2.1 預加載
顧名思義,就是預先加載。再進一步解釋就是還沒有使用該單例對象,但是,該單例對象就已經被加載到內存了。
很明顯,沒有使用該單例對象,該對象就被加載到了內存,會造成內存的浪費。
2.2 懶加載
為了避免內存的浪費,我們可以采用懶加載,即用到該單例對象的時候再創(chuàng)建。
2.3 單例模式和線程安全
(1)預加載只有一條語句return instance,這顯然可以保證線程安全。但是,我們知道預加載會造成內存的浪費。
(2)懶加載不浪費內存,但是無法保證線程的安全。首先,if判斷以及其內存執(zhí)行代碼是非原子性的。其次,new Singleton()無法保證執(zhí)行的順序性。
不滿足原子性或者順序性,線程肯定是不安全的,這是基本的常識,不再贅述。我主要講一下為什么new Singleton()無法保證順序性。我們知道創(chuàng)建一個對象分三步:
memory=allocate();//1:初始化內存空間ctorInstance(memory);//2:初始化對象instance=memory();//3:設置instance指向剛分配的內存地址jvm為了提高程序執(zhí)行性能,會對沒有依賴關系的代碼進行重排序,上面2和3行代碼可能被重新排序。我們用兩個線程來說明線程是不安全的。線程A和線程B都創(chuàng)建對象。其中,A2和A3的重排序,將導致線程B在B1處判斷出instance不為空,線程B接下來將訪問instance引用的對象。此時,線程B將會訪問到一個還未初始化的對象(線程不安全)。
2.4 保證懶加載的線程安全
我們首先想到的就是使用synchronized關鍵字。synchronized加載getInstace()函數(shù)上確實保證了線程的安全。但是,如果要經常的調用getInstance()方法,不管有沒有初始化實例,都會喚醒和阻塞線程。為了避免線程的上下文切換消耗大量時間,如果對象已經實例化了,我們沒有必要再使用synchronized加鎖,直接返回對象。
我們把sychronized加在if(instance==null)判斷語句里面,保證instance未實例化的時候才加鎖
public class Singleton {private static Singleton instance = null;private Singleton() {};public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;} }我們經過2.3的討論知道new一個對象的代碼是無法保證順序性的,因此,我們需要使用另一個關鍵字volatile保證對象實例化過程的順序性
關于violatile:Volatile如何保證有序性(禁止指令重排)_heaven殤灬的博客-CSDN博客_volatile如何保證有序性
public class Singleton {private static volatile Singleton instance = null;//volatile可以禁止JVM指令重排private Singleton() {};public static Singleton getInstance() {if (instance == null) { //判斷為空再加鎖,不為空不需要加鎖synchronized (instance) {if (instance == null) { //可能存在兩個線程第一次都判斷為空,只有1個拿到鎖,實例完后instance已經不為空了,所以需要再一次判斷,否則可能重復實例化instance = new Singleton();}}}return instance;} }到此,我們就保證了懶加載的線程安全。
3 生成器模式
定義:封裝一個復雜對象構造過程,并允許按步驟構造。
定義解釋: 我們可以將生成器模式理解為,假設我們有一個對象需要建立,這個對象是由多個組件(Component)組合而成,每個組件的建立都比較復雜,但運用組件來建立所需的對象非常簡單,所以我們就可以將構建復雜組件的步驟與運用組件構建對象分離,使用builder模式可以建立。
3.1 模式的結構和代碼示例
生成器模式結構中包括四種角色:
(1)產品(Product):具體生產器要構造的復雜對象;
(2)抽象生成器(Bulider):抽象生成器是一個接口,該接口除了為創(chuàng)建一個Product對象的各個組件定義了若干個方法之外,還要定義返回Product對象的方法(定義構造步驟);
(3)具體生產器(ConcreteBuilder):實現(xiàn)Builder接口的類,具體生成器將實現(xiàn)Builder接口所定義的方法(生產各個組件);
(4)指揮者(Director):指揮者是一個類,該類需要含有Builder接口聲明的變量。指揮者的職責是負責向用戶提供具體生成器,即指揮者將請求具體生成器類來構造用戶所需要的Product對象,如果所請求的具體生成器成功地構造出Product對象,指揮者就可以讓該具體生產器返回所構造的Product對象。(按照步驟組裝部件,并返回Product)
舉例(我們如果構建生成一臺電腦,那么我們可能需要這么幾個步驟(1)需要一個主機(2)需要一個顯示器(3)需要一個鍵盤(4)需要一個鼠標)
雖然我們具體在構建一臺主機的時候,每個對象的實際步驟是不一樣的,比如,有的對象構建了i7cpu的主機,有的對象構建了i5cpu的主機,有的對象構建了普通鍵盤,有的對象構建了機械鍵盤等。但不管怎樣,你總是需要經過一個步驟就是構建一臺主機,一臺鍵盤。對于這個例子,我們就可以使用生成器模式來生成一臺電腦,他需要通過多個步驟來生成。類圖如下:
ComputerBuilder類定義構造步驟:
HPComputerBuilder定義各個組件:
public class HPComputerBuilder extends ComputerBuilder {@Overridepublic void buildMaster() {// TODO Auto-generated method stubcomputer.setMaster("i7,16g,512SSD,1060");System.out.println("(i7,16g,512SSD,1060)的惠普主機");}@Overridepublic void buildScreen() {// TODO Auto-generated method stubcomputer.setScreen("1080p");System.out.println("(1080p)的惠普顯示屏");}@Overridepublic void buildKeyboard() {// TODO Auto-generated method stubcomputer.setKeyboard("cherry 青軸機械鍵盤");System.out.println("(cherry 青軸機械鍵盤)的鍵盤");}@Overridepublic void buildMouse() {// TODO Auto-generated method stubcomputer.setMouse("MI 鼠標");System.out.println("(MI 鼠標)的鼠標");}@Overridepublic void buildAudio() {// TODO Auto-generated method stubcomputer.setAudio("飛利浦 音響");System.out.println("(飛利浦 音響)的音響");} }Director類對組件進行組裝并生成產品
public class Director {private ComputerBuilder computerBuilder;public void setComputerBuilder(ComputerBuilder computerBuilder) {this.computerBuilder = computerBuilder;}public Computer getComputer() {return computerBuilder.getComputer();}public void constructComputer() {computerBuilder.buildComputer();computerBuilder.buildMaster();computerBuilder.buildScreen();computerBuilder.buildKeyboard();computerBuilder.buildMouse();computerBuilder.buildAudio();} } public class Test{public void static main(String args[]){Director d=new Director();d.setComputerBuilder(new HPComputerBuilder());d.constructComputer();Computer computer=d.getComputer();}}3.2 生成器模式的優(yōu)缺點
優(yōu)點
將一個對象分解為各個組件
將對象組件的構造封裝起來
可以控制整個對象的生成過程
缺點
對不同類型的對象需要實現(xiàn)不同的具體構造器的類,這可能回答大大增加類的數(shù)量
3.3 生成器模式與工廠模式的不同
生成器模式構建對象的時候,對象通常構建的過程中需要多個步驟,就像我們例子中的先有主機,再有顯示屏,再有鼠標等等,生成器模式的作用就是將這些復雜的構建過程封裝起來。工廠模式構建對象的時候通常就只有一個步驟,調用一個工廠方法就可以生成一個對象。
4 原型模式
定義:通過復制現(xiàn)有實例來創(chuàng)建新的實例,無需知道相應類的信息。
簡單地理解,其實就是當需要創(chuàng)建一個指定的對象時,我們剛好有一個這樣的對象,但是又不能直接使用,我會clone一個一毛一樣的新對象來使用;基本上這就是原型模式。關鍵字:Clone。
4.1 深拷貝和淺拷貝
淺復制:將一個對象復制后,基本數(shù)據(jù)類型的變量都會重新創(chuàng)建,而引用類型,指向的還是原對象所指向的。
深復制:將一個對象復制后,不論是基本數(shù)據(jù)類型還有引用類型,都是重新創(chuàng)建的。簡單來說,就是深復制進行了完全徹底的復制,而淺復制不徹底。clone明顯是深復制,clone出來的對象是是不能去影響原型對象的
4.2 原型模式的結構和代碼示例
Client:使用者
Prototype:接口(抽象類),聲明具備clone能力,例如java中得Cloneable接口
ConcretePrototype:具體的原型類
可以看出設計模式還是比較簡單的,重點在于Prototype接口和Prototype接口的實現(xiàn)類ConcretePrototype。原型模式的具體實現(xiàn):一個原型類,只需要實現(xiàn)Cloneable接口,覆寫clone方法,此處clone方法可以改成任意的名稱,因為Cloneable接口是個空接口,你可以任意定義實現(xiàn)類的方法名,如cloneA或者cloneB,因為此處的重點是super.clone()這句話,super.clone()調用的是Object的clone()方法。
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } }?舉例(銀行發(fā)送大量郵件,使用clone和不使用clone的時間對比):我們模擬創(chuàng)建一個對象需要耗費比較長的時間,因此,在構造函數(shù)中我們讓當前線程sleep一會
public Mail(EventTemplate et) {this.tail = et.geteventContent();this.subject = et.geteventSubject();try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}不使用clone,發(fā)送十個郵件
public static void main(String[] args) {int i = 0;int MAX_COUNT = 10;EventTemplate et = new EventTemplate("9月份信用卡賬單", "國慶抽獎活動...");long start = System.currentTimeMillis();while (i < MAX_COUNT) {// 以下是每封郵件不同的地方Mail mail = new Mail(et);mail.setContent(getRandString(5) + ",先生(女士):你的信用卡賬單..." + mail.getTail());mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");// 然后發(fā)送郵件sendMail(mail);i++;}long end = System.currentTimeMillis();System.out.println("用時:" + (end - start));}用時:10001
使用clone,發(fā)送十個郵件
public static void main(String[] args) {int i = 0;int MAX_COUNT = 10;EventTemplate et = new EventTemplate("9月份信用卡賬單", "國慶抽獎活動...");long start=System.currentTimeMillis();Mail mail = new Mail(et); while (i < MAX_COUNT) {Mail cloneMail = mail.clone();cloneMail .setContent(getRandString(5) + ",先生(女士):你的信用卡賬單..."+ mail.getTail());cloneMail .setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");sendMail(cloneMail);i++;}long end=System.currentTimeMillis();System.out.println("用時:"+(end-start));}用時:1001
4.3 總結
原型模式的本質就是clone,可以解決構建復雜對象的資源消耗問題,能再某些場景中提升構建對象的效率;還有一個重要的用途就是保護性拷貝,可以通過返回一個拷貝對象的形式,實現(xiàn)只讀的限制。
B、結構模式(7種)
適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
5 適配器模式
定義: 適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由于接口不匹配所造成的類的兼容性問題。
主要分為三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
5.1 類適配器模式
通過多重繼承目標接口和被適配者類方式來實現(xiàn)適配
舉例(將USB接口轉為VGA接口),類圖如下:
USBImpl的代碼:
?AdatperUSB2VGA 首先繼承USBImpl獲取USB的功能,其次,實現(xiàn)VGA接口,表示該類的類型為VGA。
public class AdapterUSB2VGA extends USBImpl implements VGA {@Overridepublic void projection() {super.showPPT();} }Projector將USB映射為VGA,只有VGA接口才可以連接上投影儀進行投影
public class Projector<T> {public void projection(T t) {if (t instanceof VGA) {System.out.println("開始投影");VGA v = new VGAImpl();v = (VGA) t;v.projection();} else {System.out.println("接口不匹配,無法投影");}} }test代碼
@Testpublic void test2(){//通過適配器創(chuàng)建一個VGA對象,這個適配器實際是使用的是USB的showPPT()方法VGA a=new AdapterUSB2VGA();//進行投影Projector p1=new Projector();p1.projection(a);}5.2 對象適配器模式
對象適配器和類適配器使用了不同的方法實現(xiàn)適配,對象適配器使用組合,類適配器使用繼承。
舉例(將USB接口轉為VGA接口),類圖如下:
實現(xiàn)VGA接口,表示適配器類是VGA類型的,適配器方法中直接使用USB對象。
5.3 接口適配器模式
當不需要全部實現(xiàn)接口提供的方法時,可先設計一個抽象類實現(xiàn)接口,并為該接口中每個方法提供一個默認實現(xiàn)(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現(xiàn)需求,它適用于一個接口不想使用其所有的方法的情況。
舉例(將USB接口轉為VGA接口,VGA中的b()和c()不會被實現(xiàn)),類圖如下:
AdapterUSB2VGA抽象類
AdapterUSB2VGA實現(xiàn),不用去實現(xiàn)b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {public void projection() {super.projection();} }5.4 總結
總結一下三種適配器模式的應用場景:
類適配器模式:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模式,創(chuàng)建一個新類,繼承原有的類,實現(xiàn)新的接口即可。
對象適配器模式:當希望將一個對象轉換成滿足另一個新接口的對象時,可以創(chuàng)建一個Wrapper類,持有原類的一個實例,在Wrapper類的方法中,調用實例的方法就行。
接口適配器模式:當不希望實現(xiàn)一個接口中所有的方法時,可以創(chuàng)建一個抽象類Wrapper,實現(xiàn)所有方法,我們寫別的類的時候,繼承抽象類即可。
命名規(guī)則:
我個人理解,三種命名方式,是根據(jù) src是以怎樣的形式給到Adapter(在Adapter里的形式)來命名的。
類適配器,以類給到,在Adapter里,就是將src當做類,繼承,
對象適配器,以對象給到,在Adapter里,將src作為一個對象,持有。
接口適配器,以接口給到,在Adapter里,將src作為一個接口,實現(xiàn)。
使用選擇:
根據(jù)合成復用原則,組合大于繼承。因此,類的適配器模式應該少用。
6 裝飾者模式
定義:動態(tài)的將新功能附加到對象上。在對象功能擴展方面,它比繼承更有彈性。
6.1 裝飾者模式結構圖與代碼示例
1.Component(被裝飾對象的基類)
定義一個對象接口,可以給這些對象動態(tài)地添加職責。
2.ConcreteComponent(具體被裝飾對象)
定義一個對象,可以給這個對象添加一些職責。
3.Decorator(裝飾者抽象類)
維持一個指向Component實例的引用,并定義一個與Component接口一致的接口。
4.ConcreteDecorator(具體裝飾者)
具體的裝飾對象,給內部持有的具體被裝飾對象,增加具體的職責。
被裝飾對象和修飾者繼承自同一個超類
舉例(咖啡館訂單項目:1)、咖啡種類:Espresso、ShortBlack、LongBlack、Decaf2)、調料(裝飾者):Milk、Soy、Chocolate),類圖如下:
被裝飾的對象和裝飾者都繼承自同一個超類
被裝飾的對象,不用去改造。原來怎么樣寫,現(xiàn)在還是怎么寫。
public class Coffee extends Drink {@Overridepublic float cost() {// TODO Auto-generated method stubreturn super.getPrice();}}coffee類的實現(xiàn)
public class Decaf extends Coffee {public Decaf(){super.setDescription("Decaf");super.setPrice(3.0f);} }裝飾者
裝飾者不僅要考慮自身,還要考慮被它修飾的對象,它是在被修飾的對象上繼續(xù)添加修飾。例如,咖啡里面加牛奶,再加巧克力。加糖后價格為coffee+milk。再加牛奶價格為coffee+milk+chocolate。
public class Decorator extends Drink {private Drink Obj;public Decorator(Drink Obj) {this.Obj = Obj;};@Overridepublic float cost() {// TODO Auto-generated method stubreturn super.getPrice() + Obj.cost();}@Overridepublic String getDescription() {return super.description + "-" + super.getPrice() + "&&" + Obj.getDescription();} }裝飾者實例化(加牛奶)。這里面要對被修飾的對象進行實例化。
public class Milk extends Decorator {public Milk(Drink Obj) { super(Obj);// TODO Auto-generated constructor stubsuper.setDescription("Milk");super.setPrice(2.0f);} }coffee店:初始化一個被修飾對象,修飾者實例需要對被修改者實例化,才能對具體的被修飾者進行修飾。
public class CoffeeBar {public static void main(String[] args) {Drink order;order = new Decaf();System.out.println("order1 price:" + order.cost());System.out.println("order1 desc:" + order.getDescription());System.out.println("****************");order = new LongBlack();order = new Milk(order);order = new Chocolate(order);order = new Chocolate(order);System.out.println("order2 price:" + order.cost());System.out.println("order2 desc:" + order.getDescription());} }6.2 總結
裝飾者和被裝飾者之間必須是一樣的類型,也就是要有共同的超類。在這里應用繼承并不是實現(xiàn)方法的復制,而是實現(xiàn)類型的匹配。因為裝飾者和被裝飾者是同一個類型,因此裝飾者可以取代被裝飾者,這樣就使被裝飾者擁有了裝飾者獨有的行為。根據(jù)裝飾者模式的理念,我們可以在任何時候,實現(xiàn)新的裝飾者增加新的行為。如果是用繼承,每當需要增加新的行為時,就要修改原程序了。
7 代理模式
定義:代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。
舉個例子來說明:假如說我現(xiàn)在想買一輛二手車,雖然我可以自己去找車源,做質量檢測等一系列的車輛過戶流程,但是這確實太浪費我得時間和精力了。我只是想買一輛車而已為什么我還要額外做這么多事呢?于是我就通過中介公司來買車,他們來給我找車源,幫我辦理車輛過戶流程,我只是負責選擇自己喜歡的車,然后付錢就可以了。用圖表示如下:
7.1 為什么要用代理模式?
中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現(xiàn)相同的接口。
開閉原則,增加功能:代理類除了是客戶類和委托類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委托類的功能,這樣做我們只需要修改代理類而不需要再修改委托類,符合代碼設計的開閉原則。代理類主要負責為委托類預處理消息、過濾消息、把消息轉發(fā)給委托類,以及事后對返回結果的處理等。代理類本身并不真正實現(xiàn)服務,而是同過調用委托類的相關方法,來提供特定的服務。真正的業(yè)務功能還是由委托類來實現(xiàn),但是可以在業(yè)務功能執(zhí)行的前后加入一些公共的服務。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委托類。
代理模式分為三類:1. 靜態(tài)代理 2. 動態(tài)代理 3. CGLIB代理
7.2 靜態(tài)代理
舉例(買房),類圖如下:
第一步:創(chuàng)建服務類接口
第二步:實現(xiàn)服務接口
public class BuyHouseImpl implements BuyHouse {@Overridepublic void buyHosue() {System.out.println("我要買房");} }第三步:創(chuàng)建代理類
public class BuyHouseProxy implements BuyHouse {private BuyHouse buyHouse;public BuyHouseProxy(final BuyHouse buyHouse) {this.buyHouse = buyHouse;}@Overridepublic void buyHosue() {System.out.println("買房前準備");buyHouse.buyHosue();System.out.println("買房后裝修");} }總結:
優(yōu)點:可以做到在符合開閉原則的情況下對目標對象進行功能擴展。
缺點: 代理對象與目標對象要實現(xiàn)相同的接口,我們得為每一個服務都得創(chuàng)建代理類,工作量太大,不易管理。同時接口一旦發(fā)生改變,代理類也得相應修改。
7.3 動態(tài)代理
動態(tài)代理有以下特點:
1.代理對象,不需要實現(xiàn)接口
2.代理對象的生成,是利用JDK的API,動態(tài)的在內存中構建代理對象(需要我們指定創(chuàng)建代理對象/目標對象實現(xiàn)的接口的類型)
代理類不用再實現(xiàn)接口了。但是,要求被代理對象必須有接口。
動態(tài)代理實現(xiàn):
Java.lang.reflect.Proxy類可以直接生成一個代理對象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一個代理對象
參數(shù)1:ClassLoader loader 代理對象的類加載器 一般使用被代理對象的類加載器
參數(shù)2:Class<?>[] interfaces 代理對象的要實現(xiàn)的接口 一般使用的被代理對象實現(xiàn)的接口
參數(shù)3:InvocationHandler h (接口)執(zhí)行處理類
InvocationHandler中的invoke(Object proxy, Method method, Object[] args)方法:調用代理類的任何方法,此方法都會執(zhí)行
參數(shù)3.1:代理對象(慎用)
參數(shù)3.2:當前執(zhí)行的方法
參數(shù)3.3:當前執(zhí)行的方法運行時傳遞過來的參數(shù)
第一步:編寫動態(tài)處理器
public class DynamicProxyHandler implements InvocationHandler {private Object object;public DynamicProxyHandler(final Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("買房前準備");Object result = method.invoke(object, args);System.out.println("買房后裝修");return result;} }第二步:編寫測試類
public class DynamicProxyTest {public static void main(String[] args) {BuyHouse buyHouse = new BuyHouseImpl();BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), newClass[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));proxyBuyHouse.buyHosue();} }動態(tài)代理總結:雖然相對于靜態(tài)代理,動態(tài)代理大大減少了我們的開發(fā)任務,同時減少了對業(yè)務接口的依賴,降低了耦合度。但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支持interface代理的桎梏(我們要使用被代理的對象的接口),因為它的設計注定了這個遺憾。
7.4 CGLIB代理
CGLIB 原理:動態(tài)生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。它比使用java反射的JDK動態(tài)代理要快。
CGLIB 底層:使用字節(jié)碼處理框架ASM,來轉換字節(jié)碼并生成新的類。不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。
CGLIB缺點:對于final方法,無法進行代理。
CGLIB的實現(xiàn)步驟:
第一步:建立攔截器
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("買房前準備");Object result = methodProxy.invoke(object, args);System.out.println("買房后裝修");return result;}參數(shù):Object為由CGLib動態(tài)生成的代理類實例,Method為上文中實體類所調用的被代理的方法引用,Object[]為參數(shù)值列表,MethodProxy為生成的代理類對方法的代理引用。
返回:從代理實例的方法調用返回的值。
其中,proxy.invokeSuper(obj,arg) 調用代理類實例上的proxy方法的父類方法(即實體類TargetObject中對應的方法)
第二步: 生成動態(tài)代理類
public class CglibProxy implements MethodInterceptor {private Object target;public Object getInstance(final Object target) {this.target = target;Enhancer enhancer = new Enhancer();enhancer.setSuperclass(this.target.getClass());enhancer.setCallback(this);return enhancer.create();}public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("買房前準備");Object result = methodProxy.invoke(object, args);System.out.println("買房后裝修");return result;} }這里Enhancer類是CGLib中的一個字節(jié)碼增強器,它可以方便的對你想要處理的類進行擴展,以后會經常看到它。
首先將被代理類TargetObject設置成父類,然后設置攔截器TargetInterceptor,最后執(zhí)行enhancer.create()動態(tài)生成一個代理類,并從Object強制轉型成父類型TargetObject。
第三步:測試
public class CglibProxyTest {public static void main(String[] args){BuyHouse buyHouse = new BuyHouseImpl();CglibProxy cglibProxy = new CglibProxy();BuyHouseImpl buyHouseCglibProxy = (BuyHouseImpl) cglibProxy.getInstance(buyHouse);buyHouseCglibProxy.buyHosue();} }CGLIB代理總結: CGLIB創(chuàng)建的動態(tài)代理對象比JDK創(chuàng)建的動態(tài)代理對象的性能更高,但是CGLIB創(chuàng)建代理對象時所花費的時間卻比JDK多得多。所以對于單例的對象,因為無需頻繁創(chuàng)建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由于CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final修飾的方法無法進行代理。
8 外觀模式
定義: 隱藏了系統(tǒng)的復雜性,并向客戶端提供了一個可以訪問系統(tǒng)的接口。
8.1 模式結構和代碼示例
簡單來說,該模式就是把一些復雜的流程封裝成一個接口供給外部用戶更簡單的使用。這個模式中,設計到3個角色。
1).門面角色:外觀模式的核心。它被客戶角色調用,它熟悉子系統(tǒng)的功能。內部根據(jù)客戶角色的需求預定了幾種功能的組合。(客戶調用,同時自身調用子系統(tǒng)功能)
2).子系統(tǒng)角色:實現(xiàn)了子系統(tǒng)的功能。它對客戶角色和Facade時未知的。它內部可以有系統(tǒng)內的相互交互,也可以由供外界調用的接口。(實現(xiàn)具體功能)
3).客戶角色:通過調用Facede來完成要實現(xiàn)的功能(調用門面角色)。
舉例(每個Computer都有CPU、Memory、Disk。在Computer開啟和關閉的時候,相應的部件也會開啟和關閉),類圖如下:
首先是子系統(tǒng)類:
然后是,門面類Facade
public class Computer {private CPU cpu;private Memory memory;private Disk disk;public Computer() {cpu = new CPU();memory = new Memory();disk = new Disk();}public void start() {System.out.println("Computer start begin");cpu.start();disk.start();memory.start();System.out.println("Computer start end");}public void shutDown() {System.out.println("Computer shutDown begin");cpu.shutDown();disk.shutDown();memory.shutDown();System.out.println("Computer shutDown end...");} }最后為,客戶角色
public class Client {public static void main(String[] args) {Computer computer = new Computer();computer.start();System.out.println("=================");computer.shutDown();}}8.2 優(yōu)點
- 松散耦合
使得客戶端和子系統(tǒng)之間解耦,讓子系統(tǒng)內部的模塊功能更容易擴展和維護;
- 簡單易用
客戶端根本不需要知道子系統(tǒng)內部的實現(xiàn),或者根本不需要知道子系統(tǒng)內部的構成,它只需要跟Facade類交互即可。
- 更好的劃分訪問層次
有些方法是對系統(tǒng)外的,有些方法是系統(tǒng)內部相互交互的使用的。子系統(tǒng)把那些暴露給外部的功能集中到門面中,這樣就可以實現(xiàn)客戶端的使用,很好的隱藏了子系統(tǒng)內部的細節(jié)。
9 橋接模式
定義: 將抽象部分與它的實現(xiàn)部分分離,使它們都可以獨立地變化。
9.1 案例
看下圖手機與手機軟件的類圖
增加一款新的手機軟件,需要在所有手機品牌類下添加對應的手機軟件類,當手機軟件種類較多時,將導致類的個數(shù)急劇膨脹,難以維護
手機和手機中的軟件是什么關系?
手機中的軟件從本質上來說并不是一種手機,手機軟件運行在手機中,是一種包含與被包含關系,而不是一種父與子或者說一般與特殊的關系,通過繼承手機類實現(xiàn)手機軟件類的設計是違反一般規(guī)律的。
如果Oppo手機實現(xiàn)了wifi功能,繼承它的Oppo應用商城也會繼承wifi功能,并且Oppo手機類的任何變動,都會影響其子類
換一種解決思路
從類圖上看起來更像是手機軟件類圖,涉及到手機本身相關的功能,比如說:wifi功能,放到哪個類中實現(xiàn)呢?放到OppoAppStore中實現(xiàn)顯然是不合適的
引起整個結構變化的元素有兩個,一個是手機品牌,一個是手機軟件,所以我們將這兩個點抽出來,分別進行封裝
9.2 橋接模式結構和代碼示例
類圖:
實現(xiàn):
抽象:
public abstract class Phone {protected Software software;public void setSoftware(Software software) {this.software = software;}public abstract void run();} public class Oppo extends Phone {@Overridepublic void run() {software.run();} } public class Vivo extends Phone {@Overridepublic void run() {software.run();} }對比最初的設計,將抽象部分(手機)與它的實現(xiàn)部分(手機軟件類)分離,將實現(xiàn)部分抽象成單獨的類,使它們都可以獨立地變化。整個類圖看起來像一座橋,所以稱為橋接模式
繼承是一種強耦合關系,子類的實現(xiàn)與它的父類有非常緊密的依賴關系,父類的任何變化 都會導致子類發(fā)生變化,因此繼承或者說強耦合關系嚴重影響了類的靈活性,并最終限制了可復用性
從橋接模式的設計上我們可以看出聚合是一種比繼承要弱的關聯(lián)關系,手機類和軟件類都可獨立的進行變化,不會互相影響
9.3 適用場景
橋接模式通常適用于以下場景。
當一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展時。
當一個系統(tǒng)不希望使用繼承或因為多層次繼承導致系統(tǒng)類的個數(shù)急劇增加時。
當一個系統(tǒng)需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。
9.4 優(yōu)缺點
優(yōu)點:
(1)在很多情況下,橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責原則”,復用性較差,且類的個數(shù)非常多,橋接模式是比多層繼承方案更好的解決方法,它極大減少了子類的個數(shù)。
(2)橋接模式提高了系統(tǒng)的可擴展性,在兩個變化維度中任意擴展一個維度,都不需要修改原有系統(tǒng),符合“開閉原則”。
缺點:
橋接模式的使用會增加系統(tǒng)的理解與設計難度,由于關聯(lián)關系建立在抽象層,要求開發(fā)者一開始就針對抽象層進行設計與編程。
10 組合模式
定義:有時又叫作部分-整體模式,它是一種將對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關系,使用戶對單個對象和組合對象具有一致的訪問性。
意圖:將對象組合成樹形結構以表示"部分-整體"的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
主要解決:它在我們樹型結構的問題中,模糊了簡單元素和復雜元素的概念,客戶程序可以向處理簡單元素一樣來處理復雜元素,從而使得客戶程序與復雜元素的內部結構解耦。
何時使用: 1、您想表示對象的部分-整體層次結構(樹形結構)。 2、您希望用戶忽略組合對象與單個對象的不同,用戶將統(tǒng)一地使用組合結構中的所有對象。
如何解決:樹枝和葉子實現(xiàn)統(tǒng)一接口,樹枝內部組合該接口。
關鍵代碼:樹枝內部組合該接口,并且含有內部屬性 List,里面放 Component。
組合模式的主要優(yōu)點有:
組合模式使得客戶端代碼可以一致地處理單個對象和組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了客戶端代碼;
更容易在組合體內加入新的對象,客戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”;
其主要缺點是:
設計較復雜,客戶端需要花更多時間理清類之間的層次關系;
不容易限制容器中的構件;
不容易用繼承的方法來增加構件的新功能;
10.1 模式結構和代碼示例
抽象構件(Component)角色:它的主要作用是為樹葉構件和樹枝構件聲明公共接口,并實現(xiàn)它們的默認行為。在透明式的組合模式中抽象構件還聲明訪問和管理子類的接口;在安全式的組合模式中不聲明訪問和管理子類的接口,管理工作由樹枝構件完成。
樹葉構件(Leaf)角色:是組合中的葉節(jié)點對象,它沒有子節(jié)點,用于實現(xiàn)抽象構件角色中 聲明的公共接口。
樹枝構件(Composite)角色:是組合中的分支節(jié)點對象,它有子節(jié)點。它實現(xiàn)了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法
舉例(訪問一顆樹),類圖如下:
1 組件
2 葉子
public class Leaf implements Component{private String name;public Leaf(String name) {this.name = name;}@Overridepublic void add(Component c) {}@Overridepublic void remove(Component c) {}@Overridepublic Component getChild(int i) {// TODO Auto-generated method stubreturn null;}@Overridepublic void operation() {// TODO Auto-generated method stubSystem.out.println("樹葉"+name+":被訪問!"); }}3 樹枝
public class Composite implements Component {private ArrayList<Component> children = new ArrayList<Component>();public void add(Component c) {children.add(c);}public void remove(Component c) {children.remove(c);}public Component getChild(int i) {return children.get(i);}public void operation() {for (Object obj : children) {((Component) obj).operation();}} }11 享元模式
定義:通過共享的方式高效的支持大量細粒度的對象。
主要解決:在有大量對象時,有可能會造成內存溢出,我們把其中共同的部分抽象出來,如果有相同的業(yè)務請求,直接返回在內存中已有的對象,避免重新創(chuàng)建。
何時使用: 1、系統(tǒng)中有大量對象。 2、這些對象消耗大量內存。 3、這些對象的狀態(tài)大部分可以外部化。 4、這些對象可以按照內蘊狀態(tài)分為很多組,當把外蘊對象從對象中剔除出來時,每一組對象都可以用一個對象來代替。 5、系統(tǒng)不依賴于這些對象身份,這些對象是不可分辨的。
如何解決:用唯一標識碼判斷,如果在內存中有,則返回這個唯一標識碼所標識的對象。
關鍵代碼:用 HashMap 存儲這些對象。
應用實例: 1、JAVA 中的 String,如果有則返回,如果沒有則創(chuàng)建一個字符串保存在字符串緩存池里面。
優(yōu)點:大大減少對象的創(chuàng)建,降低系統(tǒng)的內存,使效率提高。
缺點:提高了系統(tǒng)的復雜度,需要分離出外部狀態(tài)和內部狀態(tài),而且外部狀態(tài)具有固有化的性質,不應該隨著內部狀態(tài)的變化而變化,否則會造成系統(tǒng)的混亂。
簡單來說,我們抽取出一個對象的外部狀態(tài)(不能共享)和內部狀態(tài)(可以共享)。然后根據(jù)外部狀態(tài)的決定是否創(chuàng)建內部狀態(tài)對象。內部狀態(tài)對象是通過哈希表保存的,當外部狀態(tài)相同的時候,不再重復的創(chuàng)建內部狀態(tài)對象,從而減少要創(chuàng)建對象的數(shù)量。
11.1 享元模式的結構圖和代碼示例
1、Flyweight (享元抽象類):一般是接口或者抽象類,定義了享元類的公共方法。這些方法可以分享內部狀態(tài)的數(shù)據(jù),也可以調用這些方法修改外部狀態(tài)。
2、ConcreteFlyweight(具體享元類):具體享元類實現(xiàn)了抽象享元類的方法,為享元對象開辟了內存空間來保存享元對象的內部數(shù)據(jù),同時可以通過和單例模式結合只創(chuàng)建一個享元對象。
3、FlyweightFactory(享元工廠類):享元工廠類創(chuàng)建并且管理享元類,享元工廠類針對享元類來進行編程,通過提供一個享元池來進行享元對象的管理。一般享元池設計成鍵值對,或者其他的存儲結構來存儲。當客戶端進行享元對象的請求時,如果享元池中有對應的享元對象則直接返回對應的對象,否則工廠類創(chuàng)建對應的享元對象并保存到享元池。
舉例(JAVA 中的 String,如果有則返回,如果沒有則創(chuàng)建一個字符串保存在字符串緩存池里面)。類圖如下:
(1)創(chuàng)建享元對象接口
(2)創(chuàng)建具體享元對象
public class Flyweight implements IFlyweight {private String id;public Flyweight(String id){this.id = id;}@Overridepublic void print() {System.out.println("Flyweight.id = " + getId() + " ...");}public String getId() {return id;} }(3)創(chuàng)建工廠,這里要特別注意,為了避免享元對象被重復創(chuàng)建,我們使用HashMap中的key值保證其唯一。
public class FlyweightFactory {private Map<String, IFlyweight> flyweightMap = new HashMap();public IFlyweight getFlyweight(String str){IFlyweight flyweight = flyweightMap.get(str);if(flyweight == null){flyweight = new Flyweight(str);flyweightMap.put(str, flyweight);}return flyweight;}public int getFlyweightMapSize(){return flyweightMap.size();} }(4)測試,我們創(chuàng)建三個字符串,但是只會產生兩個享元對象
public class MainTest {public static void main(String[] args) {FlyweightFactory flyweightFactory = new FlyweightFactory();IFlyweight flyweight1 = flyweightFactory.getFlyweight("A");IFlyweight flyweight2 = flyweightFactory.getFlyweight("B");IFlyweight flyweight3 = flyweightFactory.getFlyweight("A");flyweight1.print();flyweight2.print();flyweight3.print();System.out.println(flyweightFactory.getFlyweightMapSize());}}C、關系模式(11種)
先來張圖,看看這11中模式的關系:
第一類:通過父類與子類的關系進行實現(xiàn)。
第二類:兩個類之間。
第三類:類的狀態(tài)。
第四類:通過中間類
12 策略模式
定義: 策略模式定義了一系列算法,并將每個算法封裝起來,使他們可以相互替換,且算法的變化不會影響到使用算法的客戶。
意圖:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。
主要解決:在有多種算法相似的情況下,使用 if…else 所帶來的復雜和難以維護。
何時使用:一個系統(tǒng)有許多許多類,而區(qū)分它們的只是他們直接的行為。
如何解決:將這些算法封裝成一個一個的類,任意地替換。
關鍵代碼:實現(xiàn)同一個接口。
優(yōu)點: 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴展性良好。
缺點: 1、策略類會增多。 2、所有策略類都需要對外暴露。
12.1 策略模式結構和示例代碼
抽象策略角色: 這個是一個抽象的角色,通常情況下使用接口或者抽象類去實現(xiàn)。對比來說,就是我們的Comparator接口。
具體策略角色: 包裝了具體的算法和行為。對比來說,就是實現(xiàn)了Comparator接口的實現(xiàn)一組實現(xiàn)類。
環(huán)境角色: 內部會持有一個抽象角色的引用,給客戶端調用。
舉例如下( 實現(xiàn)一個加減的功能),類圖如下:
1、定義抽象策略角色
2、定義具體策略角色
public class AddStrategy implements Strategy {@Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 + num2;}} public class SubstractStrategy implements Strategy {@Overridepublic int calc(int num1, int num2) {// TODO Auto-generated method stubreturn num1 - num2;}}3、環(huán)境角色
public class Environment {private Strategy strategy;public Environment(Strategy strategy) {this.strategy = strategy;}public int calculate(int a, int b) {return strategy.calc(a, b);}}4、測試
public class MainTest {public static void main(String[] args) {Environment environment=new Environment(new AddStrategy());int result=environment.calculate(20, 5);System.out.println(result);Environment environment1=new Environment(new SubstractStrategy());int result1=environment1.calculate(20, 5);System.out.println(result1);}}13 模板模式
定義:定義一個操作中算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以不改變算法的結構即可重定義該算法的某些特定步驟。
通俗點的理解就是 :完成一件事情,有固定的數(shù)個步驟,但是每個步驟根據(jù)對象的不同,而實現(xiàn)細節(jié)不同;就可以在父類中定義一個完成該事情的總方法,按照完成事件需要的步驟去調用其每個步驟的實現(xiàn)方法。每個步驟的具體實現(xiàn),由子類完成。
13.1 模式結構和代碼示例
抽象父類(AbstractClass):實現(xiàn)了模板方法,定義了算法的骨架。
具體類(ConcreteClass):實現(xiàn)抽象類中的抽象方法,即不同的對象的具體實現(xiàn)細節(jié)。
舉例( 我們做菜可以分為三個步驟 (1)備料 (2)具體做菜 (3)盛菜端給客人享用,這三部就是算法的骨架 ;然而做不同菜需要的料,做的方法,以及如何盛裝給客人享用都是不同的這個就是不同的實現(xiàn)細節(jié)。)。類圖如下:
a. 先來寫一個抽象的做菜父類:
b. 下來做兩個番茄炒蛋(EggsWithTomato)和紅燒肉(Bouilli)實現(xiàn)父類中的抽象方法
public class EggsWithTomato extends Dish {@Overridepublic void preparation() {System.out.println("洗并切西紅柿,打雞蛋。");}@Overridepublic void doing() {System.out.println("雞蛋倒入鍋里,然后倒入西紅柿一起炒。");}@Overridepublic void carriedDishes() {System.out.println("將炒好的西紅寺雞蛋裝入碟子里,端給客人吃。");}} public class Bouilli extends Dish{@Overridepublic void preparation() {System.out.println("切豬肉和土豆。");}@Overridepublic void doing() {System.out.println("將切好的豬肉倒入鍋中炒一會然后倒入土豆連炒帶燉。");}@Overridepublic void carriedDishes() {System.out.println("將做好的紅燒肉盛進碗里端給客人吃。");}}c. 在測試類中我們來做菜:
public class MainTest {public static void main(String[] args) {Dish eggsWithTomato = new EggsWithTomato();eggsWithTomato.dodish();System.out.println("-----------------------------");Dish bouilli = new Bouilli();bouilli.dodish();}}13.2 模板模式的優(yōu)點和缺點
優(yōu)點:
(1)具體細節(jié)步驟實現(xiàn)定義在子類中,子類定義詳細處理算法是不會改變算法整體結構。
(2)代碼復用的基本技術,在數(shù)據(jù)庫設計中尤為重要。
(3)存在一種反向的控制結構,通過一個父類調用其子類的操作,通過子類對父類進行擴展增加新的行為,符合“開閉原則”。
缺點:
每個不同的實現(xiàn)都需要定義一個子類,會導致類的個數(shù)增加,系統(tǒng)更加龐大。
14 觀察者模式
定義: 定義對象間的一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
主要解決:一個對象狀態(tài)改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
何時使用:一個對象(目標對象)的狀態(tài)發(fā)生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
如何解決:使用面向對象技術,可以將這種依賴關系弱化。
關鍵代碼:在抽象類里有一個 ArrayList 存放觀察者們。
優(yōu)點: 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發(fā)機制。
缺點: 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有循環(huán)依賴的話,觀察目標會觸發(fā)它們之間進行循環(huán)調用,可能導致系統(tǒng)崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標發(fā)生了變化。
14.1 模式結構圖和代碼示例
抽象被觀察者角色:也就是一個抽象主題,它把所有對觀察者對象的引用保存在一個集合中,每個主題都可以有任意數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者角色。一般用一個抽象類和接口來實現(xiàn)。
抽象觀察者角色:為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
具體被觀察者角色:也就是一個具體的主題,在集體主題的內部狀態(tài)改變時,所有登記過的觀察者發(fā)出通知。
具體觀察者角色:實現(xiàn)抽象觀察者角色所需要的更新接口,一邊使本身的狀態(tài)與制圖的狀態(tài)相協(xié)調。
舉例(有一個微信公眾號服務,不定時發(fā)布一些消息,關注公眾號就可以收到推送消息,取消關注就收不到推送消息。)類圖如下:
1、定義一個抽象被觀察者接口
2、定義一個抽象觀察者接口
public interface Observer {public void update(String message);}3、定義被觀察者,實現(xiàn)了Observerable接口,對Observerable接口的三個方法進行了具體實現(xiàn),同時有一個List集合,用以保存注冊的觀察者,等需要通知觀察者時,遍歷該集合即可。
public class WechatServer implements Subject {private List<Observer> list;private String message;public WechatServer() {list = new ArrayList<Observer>();}@Overridepublic void registerObserver(Observer o) {// TODO Auto-generated method stublist.add(o);}@Overridepublic void removeObserver(Observer o) {// TODO Auto-generated method stubif (!list.isEmpty()) {list.remove(o);}}@Overridepublic void notifyObserver() {// TODO Auto-generated method stubfor (Observer o : list) {o.update(message);}}public void setInfomation(String s) {this.message = s;System.out.println("微信服務更新消息: " + s);// 消息更新,通知所有觀察者notifyObserver();}}4、定義具體觀察者,微信公眾號的具體觀察者為用戶User
public class User implements Observer {private String name;private String message;public User(String name) {this.name = name;}@Overridepublic void update(String message) {this.message = message;read();}public void read() {System.out.println(name + " 收到推送消息: " + message);}}5、編寫一個測試類
public class MainTest {public static void main(String[] args) {WechatServer server = new WechatServer();Observer userZhang = new User("ZhangSan");Observer userLi = new User("LiSi");Observer userWang = new User("WangWu");server.registerObserver(userZhang);server.registerObserver(userLi);server.registerObserver(userWang);server.setInfomation("PHP是世界上最好用的語言!");System.out.println("----------------------------------------------");server.removeObserver(userZhang);server.setInfomation("JAVA是世界上最好用的語言!");}}15 迭代器模式
定義:提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內部表示。
簡單來說,不同種類的對象可能需要不同的遍歷方式,我們對每一種類型的對象配一個迭代器,最后多個迭代器合成一個。
主要解決:不同的方式來遍歷整個整合對象。
何時使用:遍歷一個聚合對象。
如何解決:把在元素之間游走的責任交給迭代器,而不是聚合對象。
關鍵代碼:定義接口:hasNext, next。
應用實例:JAVA 中的 iterator。
優(yōu)點: 1、它支持以不同的方式遍歷一個聚合對象。 2、迭代器簡化了聚合類。 3、在同一個聚合上可以有多個遍歷。 4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點:由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數(shù)成對增加,這在一定程度上增加了系統(tǒng)的復雜性。
15.1 模式結構和代碼示例
(1)迭代器角色(Iterator):定義遍歷元素所需要的方法,一般來說會有這么三個方法:取得下一個元素的方法next(),判斷是否遍歷結束的方法hasNext()),移出當前對象的方法remove(),
(2)具體迭代器角色(Concrete Iterator):實現(xiàn)迭代器接口中定義的方法,完成集合的迭代。
(3)容器角色(Aggregate): 一般是一個接口,提供一個iterator()方法,例如java中的Collection接口,List接口,Set接口等
(4)具體容器角色(ConcreteAggregate):就是抽象容器的具體實現(xiàn)類,比如List接口的有序列表實現(xiàn)ArrayList,List接口的鏈表實現(xiàn)LinkList,Set接口的哈希列表的實現(xiàn)HashSet等。
舉例(咖啡廳和中餐廳合并,他們兩個餐廳的菜單一個是數(shù)組保存的,一個是ArrayList保存的。遍歷方式不一樣,使用迭代器聚合訪問,只需要一種方式)
1 迭代器接口?
2 咖啡店菜單和咖啡店菜單遍歷器
public class CakeHouseMenu {private ArrayList<MenuItem> menuItems;public CakeHouseMenu() {menuItems = new ArrayList<MenuItem>();addItem("KFC Cake Breakfast","boiled eggs&toast&cabbage",true,3.99f);addItem("MDL Cake Breakfast","fried eggs&toast",false,3.59f);addItem("Stawberry Cake","fresh stawberry",true,3.29f);addItem("Regular Cake Breakfast","toast&sausage",true,2.59f);}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem = new MenuItem(name, description, vegetable, price);menuItems.add(menuItem);}public Iterator getIterator(){return new CakeHouseIterator() ;}class CakeHouseIterator implements Iterator{ private int position=0;public CakeHouseIterator(){position=0;}@Overridepublic boolean hasNext() {// TODO Auto-generated method stubif(position<menuItems.size()){return true;}return false;}@Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem =menuItems.get(position);position++;return menuItem;}};//鍏朵粬鍔熻兘浠g爜}3 中餐廳菜單和中餐廳菜單遍歷器
public class DinerMenu {private final static int Max_Items = 5;private int numberOfItems = 0;private MenuItem[] menuItems;public DinerMenu() {menuItems = new MenuItem[Max_Items];addItem("vegetable Blt", "bacon&lettuce&tomato&cabbage", true, 3.58f);addItem("Blt", "bacon&lettuce&tomato", false, 3.00f);addItem("bean soup", "bean&potato salad", true, 3.28f);addItem("hotdog", "onions&cheese&bread", false, 3.05f);}private void addItem(String name, String description, boolean vegetable,float price) {MenuItem menuItem = new MenuItem(name, description, vegetable, price);if (numberOfItems >= Max_Items) {System.err.println("sorry,menu is full!can not add another item");} else {menuItems[numberOfItems] = menuItem;numberOfItems++;}}public Iterator getIterator() {return new DinerIterator();}class DinerIterator implements Iterator {private int position;public DinerIterator() {position = 0;}@Overridepublic boolean hasNext() {// TODO Auto-generated method stubif (position < numberOfItems) {return true;}return false;}@Overridepublic Object next() {// TODO Auto-generated method stubMenuItem menuItem = menuItems[position];position++;return menuItem;}}; }4 女服務員
public class Waitress {private ArrayList<Iterator> iterators = new ArrayList<Iterator>();public Waitress() {}public void addIterator(Iterator iterator) {iterators.add(iterator);}public void printMenu() {Iterator iterator;MenuItem menuItem;for (int i = 0, len = iterators.size(); i < len; i++) {iterator = iterators.get(i);while (iterator.hasNext()) {menuItem = (MenuItem) iterator.next();System.out.println(menuItem.getName() + "***" + menuItem.getPrice() + "***" + menuItem.getDescription());}}}public void printBreakfastMenu() {}public void printLunchMenu() {}public void printVegetableMenu() {} }16 責任鏈模式
定義:如果有多個對象有機會處理請求,責任鏈可使請求的發(fā)送者和接受者解耦,請求沿著責任鏈傳遞,直到有一個對象處理了它為止。
主要解決:職責鏈上的處理者負責處理請求,客戶只需要將請求發(fā)送到職責鏈上即可,無須關心請求的處理細節(jié)和請求的傳遞,所以職責鏈將請求的發(fā)送者和請求的處理者解耦了。
何時使用:在處理消息的時候以過濾很多道。
如何解決:攔截的類都實現(xiàn)統(tǒng)一接口。
關鍵代碼:Handler 里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。
16.1 模式的結構和代碼示例
抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
具體處理者(Concrete Handler)角色:實現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
客戶類(Client)角色:創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節(jié)和請求的傳遞過程。
舉例(購買請求決策,價格不同要由不同的級別決定:組長、部長、副部、總裁)。類圖如下:
1 決策者抽象類,包含對請求處理的函數(shù),同時還包含指定下一個決策者的函數(shù)
2 客戶端以及請求
public class PurchaseRequest {private int Type = 0;private int Number = 0;private float Price = 0;private int ID = 0;public PurchaseRequest(int Type, int Number, float Price) {this.Type = Type;this.Number = Number;this.Price = Price;}public int GetType() {return Type;}public float GetSum() {return Number * Price;}public int GetID() {return (int) (Math.random() * 1000);} } public class Client {public Client() {}public PurchaseRequest sendRequst(int Type, int Number, float Price) {return new PurchaseRequest(Type, Number, Price);}}3 組長、部長。。。繼承決策者抽象類
public class GroupApprover extends Approver {public GroupApprover(String Name) {super(Name + " GroupLeader");// TODO Auto-generated constructor stub}@Overridepublic void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif (request.GetSum() < 5000) {System.out.println("**This request " + request.GetID() + " will be handled by " + this.Name + " **");} else {successor.ProcessRequest(request);}}} public class DepartmentApprover extends Approver {public DepartmentApprover(String Name) {super(Name + " DepartmentLeader");}@Overridepublic void ProcessRequest(PurchaseRequest request) {// TODO Auto-generated method stubif ((5000 <= request.GetSum()) && (request.GetSum() < 10000)) {System.out.println("**This request " + request.GetID()+ " will be handled by " + this.Name + " **");} else {successor.ProcessRequest(request);}}}4測試
public class MainTest {public static void main(String[] args) {Client mClient = new Client();Approver GroupLeader = new GroupApprover("Tom");Approver DepartmentLeader = new DepartmentApprover("Jerry");Approver VicePresident = new VicePresidentApprover("Kate");Approver President = new PresidentApprover("Bush");GroupLeader.SetSuccessor(VicePresident);DepartmentLeader.SetSuccessor(President);VicePresident.SetSuccessor(DepartmentLeader);President.SetSuccessor(GroupLeader);GroupLeader.ProcessRequest(mClient.sendRequst(1, 10000, 40));}}17 命令模式
定義:將一個請求封裝為一個對象,使發(fā)出請求的責任和執(zhí)行請求的責任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。
意圖:將一個請求封裝成一個對象,從而使您可以用不同的請求對客戶進行參數(shù)化。
主要解決:在軟件系統(tǒng)中,行為請求者與行為實現(xiàn)者通常是一種緊耦合的關系,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵御變化的緊耦合的設計就不太合適。
何時使用:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請求者"與"行為實現(xiàn)者"解耦?將一組行為抽象為對象,可以實現(xiàn)二者之間的松耦合。
如何解決:通過調用者調用接受者執(zhí)行命令,順序:調用者→接受者→命令。
17.1模式結構和代碼示例
1抽象命令類(Command)角色:聲明執(zhí)行命令的接口,擁有執(zhí)行命令的抽象方法 execute()。
2具體命令角色(Concrete Command)角色:是抽象命令類的具體實現(xiàn)類,它擁有接收者對象,并通過調用接收者的功能來完成命令要執(zhí)行的操作。
3實現(xiàn)者/接收者(Receiver)角色:執(zhí)行命令功能的相關操作,是具體命令對象業(yè)務的真正實現(xiàn)者。
4調用者/請求者(Invoker)角色:是請求的發(fā)送者,它通常擁有很多的命令對象,并通過訪問命令對象來執(zhí)行相關請求,它不直接訪問接收者。
代碼舉例(開燈和關燈),類圖如下:
1 命令抽象類
2 具體命令對象
public class TurnOffLight implements Command {private Light light;public TurnOffLight(Light light) {this.light = light;}@Overridepublic void excute() {// TODO Auto-generated method stublight.Off();}@Overridepublic void undo() {// TODO Auto-generated method stublight.On();}}3 實現(xiàn)者
public class Light {String loc = "";public Light(String loc) {this.loc = loc;}public void On() {System.out.println(loc + " On");}public void Off() {System.out.println(loc + " Off");}}4 請求者
public class Contral{public void CommandExcute(Command command) {// TODO Auto-generated method stubcommand.excute();}public void CommandUndo(Command command) {// TODO Auto-generated method stubcommand.undo();}}18 狀態(tài)模式
概述
狀態(tài)模式在日常開發(fā)中是一個非常實用的模式,可以將你的代碼逼格迅速提升一個檔次,所以讓我們開始今天的卓越之旅吧
定義
當一個對象內在狀態(tài)改變時允許改變其行為,這個對象看起來像是改變了其類。定義對于初學者沒啥用,因為字都認識卻無法理解其中的含義。必須等學完了,回過頭來看才能更加深刻的理解其含義
使用場景
你發(fā)現(xiàn)你的代碼里面存在一個很長的if else列表,而這些分支都是因為不同狀態(tài)下執(zhí)行的操作不一樣時考慮使用此模式
UML類圖
照例先上一張俺手撕的UML類圖
從上圖可見,狀態(tài)模式共有3個角色
- State
是一個接口,封裝了狀態(tài)及其行為
- ConcreteState X
State的實現(xiàn)類,表示具體的狀態(tài)
- Context
保持并切換各個狀態(tài),其持有一個State的引用。它將依賴狀態(tài)的各種操作委托給不同的狀態(tài)對象執(zhí)行。其負責與客戶端交互
實例
最近王二狗又要過生日了,近兩年他內心中是非常抗拒過生日的,因為每過一個生日就意味著自己又老一歲,離被辭退的35歲魔咒又近了一步。可惜時間是不以人的意志為轉移的,任何人都阻止不了時間的流逝,所以該過還的過。令二狗比較欣慰的時,這次過生日老婆送了他一個自己一直想要的機械鍵盤作為生日禮物... 翠花于是在二狗生日前3天在京東上下了一個單...
自從下單以來,二狗天天看物流狀態(tài)信息,心心念念著自己的機械鍵盤快點到...
這個物流系統(tǒng)就很適合使用狀態(tài)模式來開發(fā),因為此過程存在很多不同的狀態(tài),例如接單,出庫,運輸,送貨,收貨,評價等等。而訂單在每個不同的狀態(tài)下的操作可能都不一樣,例如在接單狀態(tài)下,商家就需要通知倉庫揀貨,通知用戶等等操作,其他狀態(tài)類似
下面是實例的UML類圖
第一,定義一個狀態(tài)接口
此接口定義各個狀態(tài)的統(tǒng)一操作接口
public interface LogisticsState {void doAction(JdLogistics context); }第二,定義一個物流Context類
此類持有一個LogisticsState?的引用,負責在流程中保持并切換狀態(tài)
public class JdLogistics {private LogisticsState logisticsState;public void setLogisticsState(LogisticsState logisticsState) {this.logisticsState = logisticsState;}public LogisticsState getLogisticsState() {return logisticsState;}public void doAction(){Objects.requireNonNull(logisticsState);logisticsState.doAction(this);} }第三,實現(xiàn)各種狀態(tài)類
- 接單狀態(tài)類,其需要實現(xiàn)LogisticsState接口
- 出庫狀態(tài)類
依次類推,可以建立任意多個狀態(tài)類
第四, 客戶端使用
public class StateClient {public void buyKeyboard() {//狀態(tài)的保持與切換者JdLogistics jdLogistics = new JdLogistics();//接單狀態(tài)OrderState orderState = new OrderState();jdLogistics.setLogisticsState(orderState);jdLogistics.doAction();//出庫狀態(tài)ProductOutState productOutState = new ProductOutState();jdLogistics.setLogisticsState(productOutState);jdLogistics.doAction();//運輸狀態(tài)TransportState transportState = new TransportState();jdLogistics.setLogisticsState(transportState);jdLogistics.doAction();} }輸出結果:
商家已經接單,正在處理中... 商品已經出庫... 商品正在運往天津分發(fā)中心可見,我們將每個狀態(tài)下要做的具體動作封裝到了每個狀態(tài)類中,我們只需要切換不同的狀態(tài)即可。如果不使用狀態(tài)模式,我們的代碼中可能會出現(xiàn)很長的if else列表,這樣就不便于擴展和修改了。
技術要點總結
- 必須要有一個Context類,這個類持有State接口,負責保持并切換當前的狀態(tài)。
- 狀態(tài)模式沒有定義在哪里進行狀態(tài)轉換,本例是在Context類進行的,也有人在具體的State類中轉換
當使用Context類切換狀態(tài)時,狀態(tài)類之間互相不認識,他們直接的依賴關系應該由客戶端負責。 例如,只有在接單狀態(tài)的操作完成后才應該切換到出庫狀態(tài),那么出庫狀態(tài)就對接單狀態(tài)有了依賴,這個依賴順序應該由客戶端負責,而不是在狀態(tài)內判斷。
當使用具體的State類切換時,狀態(tài)直接就可能互相認識,一個狀態(tài)執(zhí)行完就自動切換到了另一個狀態(tài)去了
優(yōu)缺點
優(yōu)點
- 增強了程序的可擴展性,因為我們很容易添加一個State
- 增強了程序的封裝性,每個狀態(tài)的操作都被封裝到了一個狀態(tài)類中
缺點
類變多了
狀態(tài)模式與策略模式
狀態(tài)模式與策略模式的UML類圖都是一樣的,從表面上看他們非常相似。特別是將狀態(tài)切換任務放在Context中做的時候就更像了,但是其背后的思想卻非常不同。
- 策略模式定義了一組可互相代替的算法,這一組算法對象完成的是同一個任務,只是使用的方式不同,例如同樣是億萬富翁,馬云通過賣東西實現(xiàn),而王思聰通過繼承實現(xiàn)。
- 狀態(tài)模式不同的狀態(tài)完成的任務完全不一樣。
19 備忘錄模式
定義: 在不破壞封裝性的前提下,捕獲一個對象的內部狀態(tài),并在該對象之外保存這個狀態(tài),以便以后當需要時能將該對象恢復到原先保存的狀態(tài)。該模式又叫快照模式。
備忘錄模式是一種對象行為型模式,其主要優(yōu)點如下。
提供了一種可以恢復狀態(tài)的機制。當用戶需要時能夠比較方便地將數(shù)據(jù)恢復到某個歷史的狀態(tài)。
實現(xiàn)了內部狀態(tài)的封裝。除了創(chuàng)建它的發(fā)起人之外,其他對象都不能夠訪問這些狀態(tài)信息。
簡化了發(fā)起人類。發(fā)起人不需要管理和保存其內部狀態(tài)的各個備份,所有狀態(tài)信息都保存在備忘錄中,并由管理者進行管理,這符合單一職責原則。
其主要缺點是:資源消耗大。如果要保存的內部狀態(tài)信息過多或者特別頻繁,將會占用比較大的內存資源。
19.1 模式結構圖和代碼示例
1發(fā)起人(Originator)角色:記錄當前時刻的內部狀態(tài)信息,提供創(chuàng)建備忘錄和恢復備忘錄數(shù)據(jù)的功能,實現(xiàn)其他業(yè)務功能,它可以訪問備忘錄里的所有信息。
2備忘錄(Memento)角色:負責存儲發(fā)起人的內部狀態(tài),在需要的時候提供這些內部狀態(tài)給發(fā)起人。
3管理者(Caretaker)角色:對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。
舉例(發(fā)起者通過備忘錄存儲信息和獲取信息),類圖如下:
1 備忘錄接口
2 備忘錄
public class Memento implements MementoIF{private String state;public Memento(String state) {this.state = state;}public String getState(){return state;}}?3 發(fā)起者
public class Originator {private String state;public String getState() {return state;}public void setState(String state) {this.state = state;}public Memento saveToMemento() {return new Memento(state);}public String getStateFromMemento(MementoIF memento) {return ((Memento) memento).getState();}}?4.管理者
public class CareTaker {private List<MementoIF> mementoList = new ArrayList<MementoIF>();public void add(MementoIF memento) {mementoList.add(memento);}public MementoIF get(int index) {return mementoList.get(index);}}20 訪問者模式
定義:將作用于某種數(shù)據(jù)結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變數(shù)據(jù)結構的前提下可以添加作用于這些元素的新的操作,為數(shù)據(jù)結構中的每個元素提供多種訪問方式。它將對數(shù)據(jù)的操作與數(shù)據(jù)結構進行分離。
訪問者(Visitor)模式是一種對象行為型模式,其主要優(yōu)點如下。
擴展性好。能夠在不修改對象結構中的元素的情況下,為對象結構中的元素添加新的功能。
復用性好。可以通過訪問者來定義整個對象結構通用的功能,從而提高系統(tǒng)的復用程度。
靈活性好。訪問者模式將數(shù)據(jù)結構與作用于結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統(tǒng)的數(shù)據(jù)結構。
符合單一職責原則。訪問者模式把相關的行為封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。
訪問者(Visitor)模式的主要缺點如下。
增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。
破壞封裝。訪問者模式中具體元素對訪問者公布細節(jié),這破壞了對象的封裝性。
違反了依賴倒置原則。訪問者模式依賴了具體類,而沒有依賴抽象類。
20.1 模式結構和代碼示例
訪問者模式包含以下主要角色。
1抽象訪問者(Visitor)角色:定義一個訪問具體元素的接口,為每個具體元素類對應一個訪問操作 visit() ,該操作中的參數(shù)類型標識了被訪問的具體元素。
2具體訪問者(ConcreteVisitor)角色:實現(xiàn)抽象訪問者角色中聲明的各個訪問操作,確定訪問者訪問一個元素時該做什么。
3抽象元素(Element)角色:聲明一個包含接受操作 accept() 的接口,被接受的訪問者對象作為 accept() 方法的參數(shù)。
4具體元素(ConcreteElement)角色:實現(xiàn)抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) ,另外具體元素中可能還包含本身業(yè)務邏輯的相關操作。
5對象結構(Object Structure)角色:是一個包含元素角色的容器,提供讓訪問者對象遍歷容器中的所有元素的方法,通常由 List、Set、Map 等聚合類實現(xiàn)。
1 抽象訪問者
public interface Visitor {abstract public void Visit(Element element); }2 具體訪問者
public class CompensationVisitor implements Visitor {@Overridepublic void Visit(Element element) {// TODO Auto-generated method stubEmployee employee = ((Employee) element);System.out.println(employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));}}3 抽象元素
public interface Element {abstract public void Accept(Visitor visitor);}4 具體元素
public class CompensationVisitor implements Visitor {@Overridepublic void Visit(Element element) {// TODO Auto-generated method stubEmployee employee = ((Employee) element);System.out.println(employee.getName() + "'s Compensation is " + (employee.getDegree() * employee.getVacationDays() * 10));}}5 對象結構
public class ObjectStructure {private HashMap<String, Employee> employees;public ObjectStructure() {employees = new HashMap();}public void Attach(Employee employee) {employees.put(employee.getName(), employee);}public void Detach(Employee employee) {employees.remove(employee);}public Employee getEmployee(String name) {return employees.get(name);}public void Accept(Visitor visitor) {for (Employee e : employees.values()) {e.Accept(visitor);}}}21 中介者模式
定義:定義一個中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合松散,且可以獨立地改變它們之間的交互。中介者模式又叫調停模式,它是迪米特法則的典型應用。
中介者模式是一種對象行為型模式,其主要優(yōu)點如下。
降低了對象之間的耦合性,使得對象易于獨立地被復用。
將對象間的一對多關聯(lián)轉變?yōu)橐粚σ坏年P聯(lián),提高系統(tǒng)的靈活性,使得系統(tǒng)易于維護和擴展。
其主要缺點是:當同事類太多時,中介者的職責將很大,它會變得復雜而龐大,以至于系統(tǒng)難以維護。
21.1 模式結構和代碼示例
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對象注冊與轉發(fā)同事對象信息的抽象方法。
具體中介者(ConcreteMediator)角色:實現(xiàn)中介者接口,定義一個 List 來管理同事對象,協(xié)調各個同事角色之間的交互關系,因此它依賴于同事角色。
抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現(xiàn)所有相互影響的同事類的公共功能。
具體同事類(Concrete Colleague)角色:是抽象同事類的實現(xiàn)者,當需要與其他同事對象交互時,由中介者對象負責后續(xù)的交互。
舉例(通過中介賣方),類圖如下:
1 抽象中介者
2 具體中介者
public class ConcreteMediator implements Mediator {private List<Colleague> colleagues = new ArrayList<Colleague>();@Overridepublic void register(Colleague colleague) {// TODO Auto-generated method stubif (!colleagues.contains(colleague)) {colleagues.add(colleague);colleague.setMedium(this);}}@Overridepublic void relay(String from, String to, String ad) {// TODO Auto-generated method stubfor (Colleague cl : colleagues) {String name = cl.getName();if (name.equals(to)) {cl.receive(from, ad);}}}}3 抽象同事類
public abstract class Colleague {protected Mediator mediator;protected String name;public Colleague(String name) {this.name = name;}public void setMedium(Mediator mediator) {this.mediator = mediator;}public String getName() {return name;}public abstract void Send(String to, String ad);public abstract void receive(String from, String ad);}4 具體同事類
public class Buyer extends Colleague {public Buyer(String name) {super(name);}@Overridepublic void Send(String to, String ad) {// TODO Auto-generated method stubmediator.relay(name, to, ad);}@Overridepublic void receive(String from, String ad) {// TODO Auto-generated method stubSystem.out.println(name + "接收到來自" + from + "的消息:" + ad);}}22.解釋器模式
定義
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
解釋器模式(Interpreter Pattern)提供了評估語言的語法或表達式的方式,它屬于行為型模式。這種模式實現(xiàn)了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。
解決
對于一些固定文法構建一個解釋句子的解釋器。
解釋器模式(Interpreter)是一種針對特定問題設計的一種解決方案。例如,匹配字符串的時候,由于匹配條件非常靈活,使得通過代碼來實現(xiàn)非常不靈活。
優(yōu)點
能夠很容易地改變和擴展文法,因為該模式使用類來表示文法規(guī)則,你可使用繼承來改變或擴展該文法。
比較容易實現(xiàn)文法,因為定義抽象語法樹中各個節(jié)點地類的實現(xiàn)大體類似,這些類都易于直接編寫。
缺點
解釋器模式為文法中的每一條規(guī)則至少定義了一個類,因此包含許多規(guī)則的文法可能難以管理和維護。
易引起類膨脹。
可利用的場景較少。
解釋器模式采用遞歸調用方法。
結構
解釋器模式包含如下角色:
AbstrExpression: 抽象表達式
TerminalExpression: 終結符表達式
NonterminalExpression: 非終結符表達式
Context: 環(huán)境類:包含解釋器之外的一些全局信息
實現(xiàn)
package interpreterpattern; /*** 聲明一個抽象的解釋操作,這個接口為抽象語法樹中所有的節(jié)點所共享*/ public abstract class AbstractExpression {public abstract void interpret(Context context); } package interpreterpattern;/*** 實現(xiàn)與文法中的終結符相關聯(lián)的解釋操作,文法中每一個終結符都有一個具體終結表達式與之相對應*/ public class TerminalExpression extends AbstractExpression{@Overridepublic void interpret(Context context) {System.out.println("終端解釋器");} } package interpreterpattern; /*** 非終結符表達式,為文法中的非終結符實現(xiàn)解釋操作。對文法中每一條規(guī)則R1、R2...Rn都需要一個具體的非終結符表達式類。*/ public class NonTerminalExpression extends AbstractExpression{@Overridepublic void interpret(Context context) {System.out.println("非終端解釋器");} } package interpreterpattern; /*** 包含解釋器之外的一些全局信息*/ public class Context {private String input;private String output;public String getInput() {return input;}public void setInput(String input) {this.input = input;}public String getOutput() {return output;}public void setOutput(String output) {this.output = output;} } package interpreterpattern; import java.util.ArrayList; import java.util.List; /*** 構建表示該文法定義的語言中一個特定的句子的抽象語法樹,調用解釋操作*/ public class InterpreterClient {public static void main(String[] args) {Context context = new Context();List<AbstractExpression> list = new ArrayList<AbstractExpression>();list.add(new TerminalExpression());list.add(new NonTerminalExpression());list.add(new TerminalExpression());list.add(new TerminalExpression());for (AbstractExpression expression : list) {expression.interpret(context);}} }實例
做了一個正則化的小例子 該標準是 [單個數(shù)字-單個小寫-單個大寫]
package interpreterpattern.demo;public abstract class AbstractExpression {public abstract boolean interpret(String info); } package interpreterpattern.demo; import java.util.HashSet; import java.util.Set;public class TerminalExpression extends AbstractExpression{private Set<String> set =new HashSet<String>();public TerminalExpression(String[] data){for(int i=0; i<data.length;i++)set.add(data[i]);}@Overridepublic boolean interpret(String info) {if(set.contains(info)){return true;}return false;} } package interpreterpattern.demo;public class NonTerminalExpression extends AbstractExpression{private AbstractExpression address=null;private AbstractExpression name=null;private AbstractExpression id=null;public NonTerminalExpression(AbstractExpression address, AbstractExpression name, AbstractExpression id) {this.address = address;this.name = name;this.id = id;}@Overridepublic boolean interpret(String info) {String s[]=info.split("-");return address.interpret(s[0])&&name.interpret(s[1])&&id.interpret(s[2]);} } package interpreterpattern.demo; public class Context {private String[] shuzis={"1","2","3","4","5","6","7","8","9","0"};private String[] xiaoxiezimus={"a","b","c","d","e","f","g","h","i","j","k","l"};private String[] daxiezimus={"A","B","C","D","E","F","G"};private AbstractExpression infomation;public Context(){AbstractExpression shuzi=new TerminalExpression(shuzis);AbstractExpression xiaoxiezimu=new TerminalExpression(xiaoxiezimus);AbstractExpression daxiezimu=new TerminalExpression(daxiezimus);infomation=new NonTerminalExpression(shuzi,xiaoxiezimu,daxiezimu);}public void jieshi(String info){boolean ok=infomation.interpret(info);if(ok) System.out.println("正確! ["+info+"] 滿足 [單個數(shù)字-單個小寫-單個大寫] 的條件");else System.out.println("錯誤! ["+info+"] 不滿足 [單個數(shù)字-單個小寫-單個大寫] 的條件");} } package interpreterpattern.demo; public class InterpreterClient {public static void main(String[] args) {Context people=new Context();people.jieshi("2-a-A");people.jieshi("11-A-5");people.jieshi("你-好-吖");people.jieshi("2aA");} }?
總結
- 上一篇: 上市 | 章泽天 : 刘强东用10秒钟时
- 下一篇: ffmpeg 的 tbr tbc 和 t