23种设计模式(下)
目錄
- 十一、享元模式(Flyweight)
- 十二、門面模式(Fa?ade)
- 十三、代理模式(Proxy)
- 十四、適配器模式(Adapter)
- 十五、中介者模式(Mediator)
- 十六、狀態模式(State)
- 十七、備忘錄模式(Memento)
- 十八、組合模式(Composite)
- 十九、迭代器模式(Iterator)
- 二十、職責鏈模式(Chain of Resposibility)
- 二十一、命令模式(Command)
- 二十二、訪問者模式(Visteor)
- 二十三、解析器模式(Interpreter)
- 什么時候不用模式
十一、享元模式(Flyweight)
動機(Motivation)
- 在軟件系統采用純粹對象方案的問題在于大量細粒度的對象會很快充斥在系統中,從而帶來很高的運行時代價——主要指內存需求方面的代價。
- 如何在避免大量細粒度對象問題的同時,讓外部客戶程序仍然能夠透明地使用面向對象的方式來進行操作?
模式定義
- 運行共享技術有效地支持大量細粒度的對象。
要點總結
- 面向對象很好地解決了抽象性的問題,但是作為運行機器中的程序實體,我們需要考慮對象的代價問題,Flyweight主要解決面向對象的代價問題,一般不觸及面向對象的抽象性問題。
- Flyweight采用對象共享的做法來降低系統中對象的個數,從而降低細粒度對象給系統帶來的壓力。如果對象被共享,我們就要保證對象的變化不會對其他使用者產生影響,所以在具體實現方面,要注意對象狀態的處理,一般設為只讀。如果系統中存在大量可以被共享的對象,可以考慮使用享元模式。
- 對象的數量太大從而導致對象內存開銷加大——什么樣的數量才算大?這需要我們仔細的根據具體應用情況進行評估,而不能憑空臆斷。
結構圖
代碼示例
- 測試主類
十二、門面模式(Fa?ade)
動機(Motivation)
- 客戶和組件中各種復雜的子系統有過多的耦合,但是子系統可能一直在變化。
- 如何簡化外部客戶程序和系統間的交互接口?如何解耦?
模式定義
- 為子系統中的一組接口提供一個一致(穩定)的界面,Fa?ade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用(復用)。
要點總結
- 從客戶程序角度來看,Fa?ade模式簡化了整個組件系統的接口,對于組件內部與外部的客戶程序來說, 達到了一種”解耦“的效果——內部子系統的任何變化不會影響到Fa?ade接口的變化。這種設計沒有一種具體的代碼結構,但是從一定高度的抽象上看滿足這樣一種模式。其實這有點像設計一個函數,參數是不變的,但是具體如何實現卻各有所差。
- Fa?ade設計模式更注重架構的層次去看整個系統,而不是單個類的層次。Fa?ade很多時候是一種架構設計模式。
- Fa?ade設計模式并非一個集裝箱,可以任意地放進任何多個對象。Fa?ade模式組件中的內部應該是”相互耦合關系比較大的一系列組件“,而不是一個簡單的功能集合。
結構圖
代碼示例
示例代碼很簡單,這里不做過多說明,其中ProductSalesman 類就是門面模式中的門面(Facade)
- 子系統內容
- 門面
- 測試主類
十三、代理模式(Proxy)
動機(Motivation)
- 在面向對象系統中,有些對象由于某種原因(比如對象創建的開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問等), 直接訪問會給使用者、或者系統結構帶來很多麻煩。
- 如何在不失去透明操作對象的同事來管理/控制這些對象特有的復雜性?增加一層間接層是軟件開發中常見的解決方式。
模式定義
- 為其他對象提供一種代理以控制(隔離,使用接口)對這對象的訪問。
要點總結
- Proxy并不一定要求保持接口完整的一致性,只要能夠實現間接控制,有時候損及一些透明性是可以接受的。
結構圖
代碼示例
代理模式是我們非常熟悉的一種模式,代碼實現也非常簡單。被代理對象和代理對象采用同一個接口。
- 被代理的對象
- 代理對象
- 測試主類
十四、適配器模式(Adapter)
動機(Motivation)
- 由于應用環境的變化,常常需要將”一些現存的對象“放在新的環境中應用,但是新環境要求的接口是這些現存對象所不滿足。
如何應對這些”遷移的變化“?
模式定義
- 將一個類的接口轉換成客戶希望的另一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
要點總結
- 在遺留代碼復用、類庫遷移等方面有用,不過從中可以看出可以使用適配器的前提是代碼功能確實是可以復用的。
結構圖
Target是一端,Adaptee是一端,他們通過Adapter適配起來。
代碼示例
我們希望將原來的Adaptee轉換為適配Target接口,這種適配功能的完成可以有兩種實現方式:類適配器和對象適配器。但是我們更加推薦使用對象適配器,因為這樣會比較靈活一點,我可以給對象適配器傳入任何滿足接口的類,而類適配器就必須和一個具體的類綁定,實際上類適配器也很少被使用。
- 被適配者和目標接口
- 兩種適配器實現的適配方式
- 測試主類
十五、中介者模式(Mediator)
動機(Motivation)
- 多個對象相互關聯的情況,對象之間常常會維持一種復雜的引用關系,如果遇到一些需求的更改,這種直接的引用關系將面臨不斷的變化。
- 在這種情況下,可以使用一種”中介對象“來管理對象間的關聯關系,避免相互交互的對象之間的緊耦合引用關系,從而更好地抵御變化。
模式定義
- 用一個中介對象來封裝(封裝變化)一系列的對象交互。中介者使各對象不需要顯式的相互引用(編譯時依賴->運行時依賴), 從而使其耦合松散(管理變化),并且可以獨立地改變它們之間的交互。
要點總結
- 將多個對象間復雜的關聯關系解耦
- Facade模式是解耦系統間(單向)的對象關聯關系;Mediator模式是解耦系統內各個對象之間(雙向)的關聯關系。
結構圖
代碼示例
在代碼中無論是PersistentDB還是PersistentFile的void getData(Object data,Midiator midiator);操作觸發后,都會觸發另一個的void getData(Object data);的發生,如果不希望觸發另一個的發生可以直接使用void getData(Object data);如果沒有中介者,可能我就需要在類中直接使用另一個對象。
- 中介者
- 相互關聯的對象
- 測試主類
十六、狀態模式(State)
動機(Motivation)
- 對象狀態如果改變,其行為也會隨之而發生變化,比如文檔處于只讀狀態,其支持的行為和讀寫狀態支持的行為就可能完全不同。
- 如何在運行時根據對象的狀態來透明地改變對象的行為?
模式定義
- 允許一個對象在其內部狀態改變時改變它的行為。從而使對象看起來似乎修改了其行為。
要點總結
- State模式將所有與一個特定狀態相關的行為都放入一個State的子對象中,在對象狀態切換時,切換相應的對象; 但同時維持State的接口,這樣實現了具體操作與狀態轉換之間的解耦。
- 轉換是原子性的
- 與Strategy模式(策略模式)類似。
結構圖
代碼示例
在代碼中我們根據需要存儲的不同數據的大小來確定使用哪種保存方式,但是對外暴露的保存數據接口(saveDataController.save())是一樣的。
- 不同狀態的處理函數
- 狀態控制環境(對用戶暴露的使用接口)。
- 測試主類
十七、備忘錄模式(Memento)
動機(Motivation)
- 某些對象的狀態轉換過程中,可能由于某中需要,要求程序能夠回溯到對象之前處于某個點的狀態。 如果使用一些公開接口來讓其他對象得到對象的狀態,便會暴露對象的細節實現。
- 如何實現對象狀態的良好保存與恢復?但同時又不會因此而破壞對象本身的封裝性、
模式定義
- 在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可以將該對象恢復到原先保存的狀態。
要點總結
- 備忘錄存儲原發器(Originator)對象的內部狀態,在需要時恢復原發器狀態。
- 有些過時。
結構圖
代碼示例
備忘錄模式使用三個類 Memento、Originator 和 CareTaker。Memento 包含了要被恢復的對象的狀態。Originator 創建并在 Memento 對象中存儲狀態。Caretaker 對象負責從 Memento 中恢復對象的狀態。在這里我們將對象的狀態簡化為了只有一個字符串來描述。
public class Memento {private String state;public Memento(String state){this.state = state;}public String getState(){return state;} } public class Originator {private String state;public void setState(String state){this.state = state;}public String getState(){return state;}public Memento saveStateToMemento(){return new Memento(state);}public void getStateFromMemento(Memento Memento){state = Memento.getState();} } import java.util.ArrayList; import java.util.List;public class CareTaker {private List<Memento> mementoList = new ArrayList<Memento>();public void add(Memento state){mementoList.add(state);}public Memento get(int index){return mementoList.get(index);} } public class MementoPatternDemo {public static void main(String[] args) {Originator originator = new Originator();CareTaker careTaker = new CareTaker();originator.setState("State #1");originator.setState("State #2");careTaker.add(originator.saveStateToMemento());originator.setState("State #3");careTaker.add(originator.saveStateToMemento());originator.setState("State #4");System.out.println("Current State: " + originator.getState()); originator.getStateFromMemento(careTaker.get(0));System.out.println("First saved State: " + originator.getState());originator.getStateFromMemento(careTaker.get(1));System.out.println("Second saved State: " + originator.getState());} }十八、組合模式(Composite)
動機(Motivation)
- 客戶代碼過多地依賴于對象容器復雜的內部實現結構,對象容器內部實現結構(而非抽象結構)的變化 引起客戶代碼的頻繁變化,帶來了代碼的維護性、擴展性等弊端。
- 如何將”客戶代碼與復雜的對象容器結構“解耦?讓對象容器自己來實現自身的復雜結構,從而使得客戶代碼就像處理簡單對象一樣來處理復雜的對象容器?
模式定義
- 將對象組合成樹形結構以表示”部分-整體“的層次結構。Composite使得用戶對單個對象和組合對象的使用具有一致性(穩定)。
要點總結
- Composite模式采用樹性結構來實現普遍存在的對象容器,從而將”一對多“的關系轉化為”一對一“的關系,使得客戶代碼可以一致地(復用)處理對象和對象容器, 無需關心處理的是單個的對象,還是組合的對象容器。
- 客戶代碼與純粹的抽象接口——而非對象容器的內部實現結構——發生依賴,從而更能”應對變化“。
- Composite模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技術來改善效率。
結構圖
代碼示例
代碼示例是非常典型的目錄和文件這種結構和整體的組合關系,文件和目錄都實現了Component ,從而方便用戶使用,而不必區分具體的類型。
import java.util.Iterator; import java.util.List; //抽象組件 public interface Component {void addFile(Component file);Component addFolder(Component folder);void removeFile(Component file);void removeFolder(Component folder);List<Component> getFiles();List<Component> getFolders();List<Component> getAll();Iterator<Component> iterator();void display(); } import java.util.Iterator; import java.util.List; //Leaf節點 public class File implements Component{private String name;public File(String name){this.name = name;}@Overridepublic void addFile(Component file) {}@Overridepublic Component addFolder(Component folder) { return null; }@Overridepublic void removeFile(Component file) {}@Overridepublic void removeFolder(Component folder) {}@Overridepublic List<Component> getFiles() { return null; }@Overridepublic List<Component> getFolders() { return null; }@Overridepublic List<Component> getAll() { return null; }@Overridepublic Iterator<Component> iterator() { return null; }@Overridepublic void display() {System.out.println(name);} } import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Folder implements Component {private String name;private List<Component> files;private List<Component> folders;public Folder(String name){this.name = name;files = new ArrayList<Component>();folders = new ArrayList<Component>();}@Overridepublic void addFile(Component file) {files.add(file);}@Overridepublic Component addFolder(Component folder) {folders.add(folder);return this;}@Overridepublic void removeFile(Component file) {files.remove(file);}@Overridepublic void removeFolder(Component folder) {folders.remove(folder);}@Overridepublic List<Component> getFiles() {return files;}@Overridepublic List<Component> getFolders() {return folders;}@Overridepublic List<Component> getAll() {List<Component> all = new ArrayList<Component>(folders);all.addAll(files);return all;}@Overridepublic Iterator<Component> iterator() {List<Component> all = new ArrayList<Component>();add(all,this);return all.iterator();}private void add(List<Component> all,Component component){if(component==null) return;all.add(component);Iterator<Component> iterator = component.getFolders().iterator();while(iterator.hasNext()){add(all,iterator.next());}all.addAll(component.getFiles());}@Overridepublic void display() {System.out.println(name);} } import java.util.Iterator; public class TestUse {public static void main(String args[]){Component root = new Folder("root");//根目錄Component folder1 = new Folder("java");Component folder2 = new Folder("c++");Component folder3 = new Folder("c#");Component file1 = new File("info.txt");root.addFolder(folder1).addFolder(folder2).addFolder(folder3).addFile(file1);//添加一級目錄folder1.addFile(new File("info.java"));Iterator<Component> iterator = root.iterator();while(iterator.hasNext()){Component component = iterator.next();if(component instanceof Folder)System.out.print("folder:");elseSystem.out.print("file:");component.display();}} }十九、迭代器模式(Iterator)
動機(Motivation)
- 集合對象內部結構常常變化異常。但對于這些集合對象,我們希望不暴露其內部結構的同時,可以讓外部客戶代碼透明地訪問其中包含的元素; 同時這種”透明遍歷“也為”同一種算法在多種集合對象上進行操作“提供了可能。
- 使用面向對象技術將這種遍歷機制抽象為”迭代器對象“為”應對變化中的集合對象“提供了一種優雅的方式。
模式定義
- 提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露(穩定)該對象的內部表示。
要點總結
- 迭代抽象:訪問一個聚合對象的內容而無需暴露它的內部表示。
- 迭代多態:為遍歷不同的集合對象提供一個統一的接口,從而支持同樣的算法在不同的集合結構上進行操作。
- 對C++來說是過時的,現在迭代器用模板,面向對象的方式性能低。
結構圖
示例代碼
在java的集合中,很多實現了迭代器模式。
二十、職責鏈模式(Chain of Resposibility)
動機(Motivation)
- 一個請求可能被多個對象處理,但是每個請求在運行時只能有一個接收者,如果顯式指定,將必不可少地帶來請求發送者與接收者的緊耦合。
- 如何使請求的發送者不需要指定具體的接收者?讓請求的接收者自己在運行時決定來處理請求,從而使兩者解耦。
模式定義
- 使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象處理它為止。
要點總結
- 應用于”一個請求可能有多個接受者,但是最后真正的接受者只有一個“,這時候請求發送者與接受者有可能出現”變化脆弱“的癥狀,職責鏈解耦。
- 有些過時。
結構圖
示例代碼
代碼中Handler1、Handler2、Handler3組成一種職責鏈,我們將所有處理都給Handler1 如果它無法處理就發送給下一個鏈中的處理者。在實際應用的時候我們需要寫一個缺省的處理方式。
//處理者 public interface IHandler {int handleRequest(int n);void setNextHandler(Handler next); } //第一個具體處理者,處理小于0的 public class Handler1 implements Handler {private Handler next;@Overridepublic int handleRequest(int n) {if(n<0) return -n;else{if(next==null)throw new NullPointerException("next 不能為空");return next.handleRequest(n);}}@Overridepublic void setNextHandler(Handler next) {this.next = next;} } //第二個具體處理者,處理>=0但小于10的 public class Handler2 implements Handler {private Handler next;@Overridepublic int handleRequest(int n) {if(n<10) return n*n;else{if(next==null)throw new NullPointerException("next 不能為空");return next.handleRequest(n);}}@Overridepublic void setNextHandler(Handler next) {this.next = next;} } //第三個具體處理者,處理>=0但小于10的 public class Handler3 implements Handler {private Handler next;@Overridepublic int handleRequest(int n) {if(n<=Integer.MAX_VALUE) return n;else{if(next==null)throw new NullPointerException("next 不能為空");return next.handleRequest(n);}}@Overridepublic void setNextHandler(Handler next) {this.next = next;} }- 測試主類
二十一、命令模式(Command)
動機(Motivation)
- ”行為請求者“與”行為實現者“通常呈現一種”緊耦合“。但在某些場合——比如需要對行為進行”記錄、撤銷、事務“等處理,這種無法抵御變化的緊耦合是不合適的。
- 在這種情況下,如何將”行為請求者“與”行為實現者“解耦?將一組行為抽象為對象,可以實現二者之間的松耦合。
模式定義
- 將一個請求(行為)封裝成一個對象,從而使你可用不用的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤銷的操作。
要點總結
- Command模式的根本目的在于將”行為請求者“與”行為實現者“解耦,在面向對象語言中,常見的實現手段是”將行為抽象為對象“。
- 與C++中的函數對象類似,C++函數對象以函數簽名來定義行為接口規范,更靈活性能更高。
結構圖
代碼示例
代碼中我們將行為的實現變為了類,通過不同命令作為中介,行為請求者和這些中介打交道,就可以執行相應的行為,并且行為實現者可以變動。
- 不同的命令接口
- 行為請求者
- 行為實現者
- 測試主類
二十二、訪問者模式(Visteor)
動機(Motivation)
- 由于需求的變化,某些類層次結構中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改, 將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。
- 如何在不更改類層次結構的前提下,在運行時根據需要透明地為類層次結構上的各個類動態添加新的操作,從而避免上面的問題?
模式定義
- 表示一個作用與某對象結構中的各元素的操作。使得可以在不改變(穩定)各元素的類的前提下定義(擴展) 作用于這些元素的新操作(變化)。
要點總結
- Visitor模式通過所謂的雙重分發來實現在不更改(編譯時)Element類層次結構的前提下,在運行時 透明地為類層次結構上的各個類動態添加新的操作(支持變化)。
- 所謂雙重分發Visitor模式中間包括了兩個多態分發:第一個為accept方法的多態辨析;第二個為visitElement方法的多態辨析。
- Visitor模式的最大缺點在于擴展類層次結構(添加新的Element子類),會導致Visitor類的改變, 因此Visitor模式適用于“Element類層次結構穩定,而其中的操作卻經常面臨頻繁改動”。
- 由于要求太過嚴格(需要知道Element的所有子類),我們很少使用。
結構圖
示例代碼
在代碼中原來的代碼已經固定下來了,但是固定的代碼中public void accept(Visitor visitor)預示了將來可能有擴展的操作。之后Visitor 接口及其子類實現了方法的擴展,如果要使用這些擴展的代碼的話,我們需要調用public void accept(Visitor visitor)這個方法。不過這里只有一個Visitor 的子類,也就是擴展了一個功能,如果需要擴展其他功能的話,寫這個接口的其他子類并替換public void accept(Visitor visitor)的參數即可。
- 已經固定的部分,其中public void accept(Visitor visitor)預示了將來可能有擴展的操作。
- 需要為子類新添加的方法,我們需要知道有多少子類,為每個子類都寫一個visit()方法
- 測試主類
二十三、解析器模式(Interpreter)
動機(Motivation)
- 如果某一特定領域的問題比較復雜,類似的結構不斷重復出現,如果使用普通的編程方式來實現將面臨非常頻繁的變化。
- 在這種情況下,將特定領域的問題表達為某種語法規則下的句子,然后構建一個解釋器來解釋這樣的句子,從而達到解決問題的目的。
- 在特定領域內,某些變化雖然頻繁,但可以抽象為某種規則。這時候,結合特定領域,將問題抽象為語法規則,從而給出該領域下的一般性解決方案。
模式定義
- 給定一個語言,定義它的文法的一種表示,并定義一種解釋器,這個解釋器使用該表示來解釋語言中的句子。
要點總結
- Interpreter模式的應用場合是Interpreter模式應用中的難點,只有滿足“業務規則頻繁變化,且類似的結構不斷重復出現, 并且容易抽象為語法規則的問題”才適合使用Interpreter模式。
- 使用Interpreter模式來表示文法規則,從而可以使用面向對象技巧來方便地“擴展”文法。
- Interpreter模式適合簡單的文法表示,對于復雜的文法表示需要求助語法分析器標準工具。
結構圖
示例代碼
這是一種不太常用的設計模式,這里不給出示例代碼。
什么時候不用模式
學習設計模式之后并不是一定需要使用設計模式,比如以下的一些情況,不要盲目的追求模式的使用,更多的是在實際的項目中體會。
- 代碼可讀性很差時
- 需求理解還很淺時
- 變化沒有顯現時
- 不是系統的關鍵依賴點
- 項目沒有復用價值時
- 項目將要發布時
總結
以上是生活随笔為你收集整理的23种设计模式(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 23种设计模式(上)
- 下一篇: Matab 读取修改 XML