設計模式01

轉自:https://blog.csdn.net/u013499771/article/details/50730908
題目: Java 之 23 種設計模式解析
一、設計模式概述
總體來說設計模式分爲三大類:
創建型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式, 共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行爲型模式, 共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
具體如下:
其中創建型有:
一、 Singleton,單例模式:保證一個類只有一個實例,並提供一個訪問它的全局訪問點
二、 Abstract Factory,抽象工廠:提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們的具體類。
三、 Factory Method,工廠方法:定義一個用於創建對象的接口,讓子類決定實例化哪一個類, Factory Method 使一個類的實例化延遲到了子類。
四、 Builder,建造模式:將一個複雜對象的構建與他的表示相分離,使得同樣的構建過程可以創建不同的表示。
五、 Prototype,原型模式:用原型實例指定創建對象的種類,並且通過拷貝這些原型來創建新的對象。
行爲型有:
六、 Iterator,迭代器模式:提供一個方法順序訪問一個聚合對象的各個元素,而又不需要暴露該對象的內部表示。
七、 Observer,觀察者模式:定義對象間一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知自動更新。
八、 Template Method,模板方法:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中, TemplateMethod 使得子類可以不改變一個算法的結構即可以重定義該算法得某些特定步驟。
九、 Command,命令模式:將一個請求封裝爲一個對象,從而使你可以用不同的請求對客戶進行參數化,對請求排隊和記錄請求日誌,以及支持可撤銷的操作。
十、 State,狀態模式:允許對象在其內部狀態改變時改變他的行爲。對象看起來似乎改變了他的類。
十一、 Strategy,策略模式:定義一系列的算法,把他們一個個封裝起來,並使他們可以互相替換,本模式使得算法可以獨立於使用它們的客戶。
十二、 China of Responsibility,職責鏈模式:使多個對象都有機會處理請求,從而避免請求的送發者和接收者之間的耦合關係
十三、 Mediator,中介者模式:用一箇中介對象封裝一些列的對象交互。
十四、 Visitor,訪問者模式:表示一個作用於某對象結構中的各元素的操作,它使你可以在不改變各元素類的前提下定義作用於這個元素的新操作。
十五、 Interpreter,解釋器模式:給定一個語言,定義他的文法的一個表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
十六、 Memento,備忘錄模式:在不破壞對象的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。
結構型有:
十七、 Composite,組合模式:將對象組合成樹形結構以表示部分整體的關係, Composite 使得用戶對單個對象和組合對象的使用具有一致性。
十八、 Facade,外觀模式:爲子系統中的一組接口提供一致的界面, fa?ade提供了一高層接口,這個接口使得子系統更容易使用。
十九、 Proxy,代理模式:爲其他對象提供一種代理以控制對這個對象的訪問
二十、 Adapter,適配器模式:將一類的接口轉換成客戶希望的另外一個接口,
Adapter 模式使得原本由於接口不兼容而不能一起工作那些類可以一起工作。
二十一、 Decrator,裝飾模式:動態地給一個對象增加一些額外的職責,就增加的功能來說, Decorator 模式相比生成子類更加靈活。
二十二、 Bridge,橋模式:將抽象部分與它的實現部分相分離,使他們可以獨立的變化。
二十三、 Flyweight,享元模式
其實還有兩類:併發型模式和線程池模式。用一個圖片來整體描述一下:
二、設計模式的六大原則
總原則:開閉原則( Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,而是要擴展原有代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類等,後面的具體設計中我們會提到這點。
1、單一職責原則
不要存在多於一個導致類變更的原因,也就是說每個類應該實現單一的職責,如若不然,就應該把類拆分。
2、里氏替換原則( Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP 是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。 —— From Baidu 百科
歷史替換原則中,子類對父類的方法儘量不要重寫和重載。因爲父類代表了定義好的結構,通過這個規範的接口與外界交互,子類不應該隨便破壞它。
3、依賴倒轉原則( Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:面向接口編程,依賴於抽象而不依賴於具體。寫代碼時用到具體類時,不與具體類交互,而與具體類的上層接口交互。
4、接口隔離原則( Interface Segregation Principle)
這個原則的意思是:每個接口中不存在子類用不到卻必須實現的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。
5、迪米特法則(最少知道原則)( Demeter Principle)
就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過 public 方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。
最少知道原則的另一個表達方式是:只與直接的朋友通信。類之間只要有耦合關係,就叫朋友關係。耦合分爲依賴、關聯、聚合、組合等。我們稱出現爲成員變量、方法參數、方法返回值中的類爲直接朋友。局部變量、臨時變量則不是直接的朋友。我們要求陌生的類不要作爲局部變量出現在類中。
6、合成複用原則( Composite Reuse Principle)
原則是儘量首先使用合成/聚合的方式,而不是使用繼承。
三、 Java 的 23 中設計模式
A、創建模式
從這一塊開始,我們詳細介紹 Java 中 23 種設計模式的概念,應用場景等情況,並結合他
們的特點及設計模式的原則進行分析。
首先,簡單工廠模式不屬於 23 中涉及模式,簡單工廠一般分爲:普通簡單工廠、多方法簡
單工廠、靜態方法簡單工廠。
0、簡單工廠模式
簡單工廠模式模式分爲三種:
01、普通
就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。首先看下關係圖:
舉例如下:(我們舉一個發送郵件和短信的例子)
首先,創建二者的共同接口:
[java] view plaincopy

  1. public interface Sender {
  2. public void Send();
  3. }
    其次,創建實現類:
    [java] view plaincopy
  4. public class MailSender implements Sender {
  5. @Override
  6. public void Send() {
  7. System.out.println(“this is mailsender!”);
  8. }
  9. }
    [java] view plaincopy
  10. public class SmsSender implements Sender {
  11. @Override
  12. public void Send() {
  13. System.out.println(“this is sms sender!”);
  14. }
  15. }
    最後,建工廠類:
    [java] view plaincopy
  16. public class SendFactory {
  17. public Sender produce(String type) {
  18. if (“mail”.equals(type)) {
  19. return new MailSender();
  20. } else if (“sms”.equals(type)) {
  21. return new SmsSender();
  22. } else {
  23. System.out.println(“請輸入正確的類型!”);
  24. return null;
  25. }
  26. }
  27. }
    我們來測試下:
  28. public class FactoryTest {
  29. public static void main(String[] args) {
  30. SendFactory factory = new SendFactory();
  31. Sender sender = factory.produce(“sms”);
  32. sender.Send();
  33. }
  34. }
    輸出: this is sms sender!
    02、多個方法
    是對普通工廠方法模式的改進,在普通工廠方法模式中,如果傳遞的字符串出錯,則不能正
    確創建對象,而多個工廠方法模式是提供多個工廠方法,分別創建對象。關係圖:
    將上面的代碼做下修改,改動下 SendFactory 類就行,如下:
    [java] view plaincopypublic class SendFactory {
    public Sender produceMail(){
  35. return new MailSender();
  36. }
  37. public Sender produceSms(){
  38. return new SmsSender();
  39. }
  40. }
    測試類如下:
    [java] view plaincopy
  41. public class FactoryTest {
  42. public static void main(String[] args) {
  43. SendFactory factory = new SendFactory();
  44. Sender sender = factory.produceMail();
  45. sender.Send();
  46. }
  47. }
    輸出: this is mailsender!
    03、多個靜態方法
    將上面的多個工廠方法模式裏的方法置爲靜態的,不需要創建實例,直接調用即可。
    [java] view plaincopy
  48. public class SendFactory {
  49. public static Sender produceMail(){
  50. return new MailSender();
  51. }
  52. public static Sender produceSms(){
  53. return new SmsSender();
  54. }
  55. }
    [java] view plaincopy
  56. public class FactoryTest {
  57. public static void main(String[] args) {
  58. Sender sender = SendFactory.produceMail();
  59. sender.Send();
  60. }
  61. }
    輸出: this is mailsender!
    總體來說,工廠模式適合:凡是出現了大量的產品需要創建,並且具有共同的接口時,可以
    通過工廠方法模式進行創建。在以上的三種模式中,第一種如果傳入的字符串有誤,不能正
    確創建對象,第三種相對於第二種,不需要實例化工廠類,所以,大多數情況下,我們會選
    用第三種——靜態工廠方法模式。
    1、工廠方法模式( Factory Method)
    簡單工廠模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須
    對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?
    就用到工廠方法模式,創建一個工廠接口和創建多個工廠實現類,這樣一旦需要增加新的功
    能,直接增加新的工廠類就可以了,不需要修改之前的代碼。
    請看例子:
    [java] view plaincopy
  62. public interface Sender {
  63. public void Send();
  64. }
    兩個實現類:
    [java] view plaincopy
  65. public class MailSender implements Sender {
  66. @Override
  67. public void Send() {
  68. System.out.println(“this is mailsender!”);
  69. }
  70. }
    [java] view plaincopy
  71. public class SmsSender implements Sender {
  72. @Override
  73. public void Send() {
  74. System.out.println(“this is sms sender!”);
  75. }
  76. }
    兩個工廠類:
    [java] view plaincopy
  77. public class SendMailFactory implements Provider {
  78. @Override
  79. public Sender produce(){
  80. return new MailSender();
  81. }
  82. }
    [java] view plaincopy
  83. public class SendSmsFactory implements Provider{
  84. @Override
  85. public Sender produce() {
  86. return new SmsSender();
  87. }
  88. }
    在提供一個接口:
    [java] view plaincopy
  89. public interface Provider {
  90. public Sender produce();
  91. }
    測試類:
    [java] view plaincopy
  92. public class Test {
  93. public static void main(String[] args) {
  94. Provider provider = new SendMailFactory();
  95. Sender sender = provider.produce();
  96. sender.Send();
  97. }
  98. }
    其實這個模式的好處就是,如果你現在想增加一個功能:發及時信息,則只需做一個實現類,
    實現 Sender 接口,同時做一個工廠類,實現 Provider 接口,就 OK 了,無需去改動現成的
    代碼。這樣做,拓展性較好!
    2、抽象工廠模式
    工廠方法模式和抽象工廠模式不好分清楚,他們的區別如下:
    工廠方法模式:
    一個抽象產品類,可以派生出多個具體產品類。
    一個抽象工廠類,可以派生出多個具體工廠類。
    每個具體工廠類只能創建一個具體產品類的實例。
    抽象工廠模式:
    多個抽象產品類,每個抽象產品類可以派生出多個具體產品類。
    一個抽象工廠類,可以派生出多個具體工廠類。
    每個具體工廠類可以創建多個具體產品類的實例,也就是創建的是一個產品線下的多個產
    品。
    區別:
    工廠方法模式只有一個抽象產品類,而抽象工廠模式有多個。
    工廠方法模式的具體工廠類只能創建一個具體產品類的實例,而抽象工廠模式可以創建多
    個。
    工廠方法創建 “一種” 產品,他的着重點在於"怎麼創建",也就是說如果你開發,你的大量
    代碼很可能圍繞着這種產品的構造,初始化這些細節上面。也因爲如此,類似的產品之間有
    很多可以複用的特徵,所以會和模版方法相隨。
    抽象工廠需要創建一些列產品,着重點在於"創建哪些"產品上,也就是說,如果你開發,你
    的主要任務是劃分不同差異的產品線,並且儘量保持每條產品線接口一致,從而可以從同一
    個抽象工廠繼承。
    對於 java 來說,你能見到的大部分抽象工廠模式都是這樣的:
    —它的裏面是一堆工廠方法,每個工廠方法返回某種類型的對象。
    比如說工廠可以生產鼠標和鍵盤。那麼抽象工廠的實現類(它的某個具體子類)的對象都可
    以生產鼠標和鍵盤,但可能工廠 A 生產的是羅技的鍵盤和鼠標,工廠 B 是微軟的。
    這樣 A 和 B 就是工廠,對應於抽象工廠;
    每個工廠生產的鼠標和鍵盤就是產品,對應於工廠方法;
    用了工廠方法模式,你替換生成鍵盤的工廠方法,就可以把鍵盤從羅技換到微軟。但是用了
    抽象工廠模式,你只要換家工廠,就可以同時替換鼠標和鍵盤一套。如果你要的產品有幾十
    個,當然用抽象工廠模式一次替換全部最方便(這個工廠會替你用相應的工廠方法)
    所以說抽象工廠就像工廠,而工廠方法則像是工廠的一種產品生產線
    3、單例模式( Singleton)
    單例對象( Singleton)是一種常用的設計模式。在 Java 應用中,單例對象能保證在一個 JVM
    中,該對象只有一個實例存在。這樣的模式有幾個好處:
    1、某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷。
    2、省去了 new 操作符,降低了系統內存的使用頻率,減輕 GC 壓力。
    3、有些類如交易所的核心交易引擎,控制着交易流程,如果該類可以創建多個的話,系統完全
    亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),所以只有使用單例模式,
    才能保證核心交易服務器獨立控制整個流程。
    首先我們寫一個簡單的單例類:
    [java] view plaincopy
  99. public class Singleton {
  100. /* 持有私有靜態實例,防止被引用,此處賦值爲 null,目的是實現延遲加載 */
  101. private static Singleton instance = null;
  102. /* 私有構造方法,防止被實例化 */
  103. private Singleton() {
  104. }
  105. /* 靜態工程方法,創建實例 */
  106. public static Singleton getInstance() {
  107. if (instance == null) {
  108. instance = new Singleton();
  109. return instance;
  110. }
  111. /* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
  112. public Object readResolve() {
  113. return instance;
  114. }
  115. }
    這個類可以滿足基本要求,但是,像這樣毫無線程安全保護的類,如果我們把它放入多線程
    的環境下,肯定就會出現問題了,如何解決?我們首先會想到對 getInstance 方法加
    synchronized 關鍵字,如下:
    [java] view plaincopy
  116. public static synchronized Singleton getInstance() {
  117. if (instance == null) {
  118. instance = new Singleton();
  119. }
  120. return instance;
  121. }
    但是, synchronized 關鍵字鎖住的是這個對象,這樣的用法,在性能上會有所下降,因爲
    每次調用 getInstance(),都要對對象上鎖,事實上,只有在第一次創建對象的時候需要加
    鎖,之後就不需要了,所以,這個地方需要改進。我們改成下面這個:
    [java] view plaincopy
  122. public static Singleton getInstance() {
  123. if (instance == null) {
  124. synchronized (instance) {
  125. if (instance == null) {
  126. instance = new Singleton();
  127. }
  128. }
  129. }
  130. return instance;
  131. }
    似乎解決了之前提到的問題,將 synchronized 關鍵字加在了內部,也就是說當調用的時候
    是不需要加鎖的,只有在 instance 爲 null,並創建對象的時候才需要加鎖,性能有一定的提
    升。但是,這樣的情況,還是有可能有問題的,看下面的情況:在 Java 指令中創建對象和
    賦值操作是分開進行的,也就是說 instance = new Singleton();語句是分兩步執行的。但是
    JVM 並不保證這兩個操作的先後順序,也就是說有可能 JVM 會爲新的 Singleton 實例分配
    空間,然後直接賦值給 instance 成員,然後再去初始化這個 Singleton 實例。這樣就可能出
    錯了,我們以 A、 B 兩個線程爲例:
    a>A、 B 線程同時進入了第一個 if 判斷
    b>A 首先進入 synchronized 塊,由於 instance 爲 null,所以它執行 instance = new
    Singleton();
    c>由於 JVM 內部的優化機制, JVM 先畫出了一些分配給 Singleton 實例的空白內存,並賦
    值給 instance 成員(注意此時 JVM 沒有開始初始化這個實例),然後 A 離開了 synchronized
    塊。
    d>B 進入 synchronized 塊,由於 instance 此時不是 null,因此它馬上離開了 synchronized
    塊並將結果返回給調用該方法的程序。
    e>此時 B 線程打算使用 Singleton 實例,卻發現它沒有被初始化,於是錯誤發生了。
    所以程序還是有可能發生錯誤,其實程序在運行過程是很複雜的,從這點我們就可以看出,
    尤其是在寫多線程環境下的程序更有難度,有挑戰性。我們對該程序做進一步優化:
    [java] view plaincopy
  132. private static class SingletonFactory{
  133. private static Singleton instance = new Singleton();
  134. }
  135. public static Singleton getInstance(){
  136. return SingletonFactory.instance;
  137. }
    實際情況是,單例模式使用內部類來維護單例的實現, JVM 內部的機制能夠保證當一個類
    被加載的時候,這個類的加載過程是線程互斥的。這樣當我們第一次調用 getInstance 的時
    候, JVM 能夠幫我們保證 instance 只被創建一次,並且會保證把賦值給 instance 的內存初
    始化完畢,這樣我們就不用擔心上面的問題。同時該方法也只會在第一次調用的時候使用互
    斥機制,這樣就解決了低性能問題。這樣我們暫時總結一個完美的單例模式:
    [java] view plaincopy
  138. public class Singleton {
  139. /* 私有構造方法,防止被實例化 */
  140. private Singleton() {
  141. }
  142. /* 此處使用一個內部類來維護單例 */
  143. private static class SingletonFactory {
  144. private static Singleton instance = new Singleton();
  145. }
  146. /* 獲取實例 */
  147. public static Singleton getInstance() {
  148. return SingletonFactory.instance;
  149. }
  150. /* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
  151. public Object readResolve() {
  152. return getInstance();
  153. }
  154. }
    其實說它完美,也不一定,如果在構造函數中拋出異常,實例將永遠得不到創建,也會出錯。
    所以說,十分完美的東西是沒有的,我們只能根據實際情況,選擇最適合自己應用場景的實
    現方法。也有人這樣實現:因爲我們只需要在創建類的時候進行同步,所以只要將創建和
    getInstance()分開,單獨爲創建加 synchronized 關鍵字,也是可以的:
    [java] view plaincopy
  155. public class SingletonTest {
  156. private static SingletonTest instance = null;
  157. private SingletonTest() {
  158. }
  159. private static synchronized void syncInit() {
  160. if (instance == null) {
  161. instance = new SingletonTest();
  162. }
  163. }
  164. public static SingletonTest getInstance() {
  165. if (instance == null) {
  166. syncInit();
  167. }
  168. return instance;
  169. }
  170. }
    考慮性能的話,整個程序只需創建一次實例,所以性能也不會有什麼影響。
    補充: 採用"影子實例"的辦法爲單例對象的屬性同步更新
    [java] view plaincopy
  171. public class SingletonTest {
  172. private static SingletonTest instance = null;
  173. private Vector properties = null;
  174. public Vector getProperties() {
  175. return properties;
  176. }
  177. private SingletonTest() {
  178. }
  179. private static synchronized void syncInit() {
  180. if (instance == null) {
  181. instance = new SingletonTest();
  182. }
  183. }
  184. public static SingletonTest getInstance() {
  185. if (instance == null) {
  186. syncInit();
  187. }
  188. return instance;
  189. }
  190. public void updateProperties() {
  191. SingletonTest shadow = new SingletonTest();
  192. properties = shadow.getProperties();
  193. }
  194. }
    通過單例模式的學習告訴我們:
    1、單例模式理解起來簡單, 但是具體實現起來還是有一定的難度。
    2、 synchronized 關鍵字鎖定的是對象,在用的時候,一定要在恰當的地方使用(注意需要
    使用鎖的對象和過程,可能有的時候並不是整個對象及整個過程都需要鎖)。
    到這兒,單例模式基本已經講完了,結尾處,筆者突然想到另一個問題,就是採用類的靜態
    方法,實現單例模式的效果,也是可行的,此處二者有什麼不同?
    首先,靜態類不能實現接口。(從類的角度說是可以的,但是那樣就破壞了靜態了。因爲接
    口中不允許有 static 修飾的方法,所以即使實現了也是非靜態的)
    其次,單例可以被延遲初始化, 靜態類一般在第一次加載是初始化。之所以延遲加載,是因
    爲有些類比較龐大,所以延遲加載有助於提升性能。
    再次,單例類可以被繼承,他的方法可以被覆寫。但是靜態類內部方法都是 static,無法被
    覆寫。
    最後一點,單例類比較靈活,畢竟從實現上只是一個普通的 Java 類,只要滿足單例的基本
    需求,你可以在裏面隨心所欲的實現一些其它功能,但是靜態類不行。從上面這些概括中,
    基本可以看出二者的區別,但是,從另一方面講,我們上面最後實現的那個單例模式,內部
    就是用一個靜態類來實現的,所以,二者有很大的關聯,只是我們考慮問題的層面不同罷了。
    兩種思想的結合,才能造就出完美的解決方案,就像 HashMap 採用數組+鏈表來實現一樣,
    其實生活中很多事情都是這樣,單用不同的方法來處理問題,總是有優點也有缺點,最完美
    的方法是,結合各個方法的優點,才能最好的解決問題!
    4、建造者模式( Builder)
    5、原型模式( Prototype)
    原型模式雖然是創建型的模式,但是與工程模式沒有關係,從名字即可看出,該模式的思想
    就是將一個對象作爲原型,對其進行復制、克隆,產生一個和原對象類似的新對象。本小結
    會通過對象的複製,進行講解。在 Java 中,複製對象是通過 clone()實現的,先創建一個原
    型類:
    [java] view plaincopy
  195. public class Prototype implements Cloneable {
  196. public Object clone() throws CloneNotSupportedException {
  197. Prototype proto = (Prototype) super.clone();
  198. return proto;
  199. }
  200. }
    很簡單,一個原型類,只需要實現 Cloneable 接口,覆寫 clone 方法,此處 clone 方法可以
    改成任意的名稱,因爲 Cloneable 接口是個空接口,你可以任意定義實現類的方法名,如
    cloneA 或者 cloneB,因爲此處的重點是 super.clone()這句話,super.clone()調用的是 Object
    的 clone()方法,而在 Object 類中, clone()是 native 的,具體怎麼實現,我會在另一篇文章
    中,關於解讀 Java 中本地方法的調用,此處不再深究。在這兒,我將結合對象的淺複製和
    深複製來說一下,首先需要了解對象深、淺複製的概念:
    淺複製:將一個對象複製後,基本數據類型的變量都會重新創建,而引用類型,指向的還是
    原對象所指向的。
    深複製:將一個對象複製後,不論是基本數據類型還有引用類型,都是重新創建的。簡單來
    說,就是深複製進行了完全徹底的複製,而淺複製不徹底。
    此處,寫一個深淺複製的例子:
    [java] view plaincopy
  201. public class Prototype implements Cloneable, Serializable {
  202. private static final long serialVersionUID = 1L;
  203. private String string;
  204. private SerializableObject obj;
  205. /* 淺複製 */
  206. public Object clone() throws CloneNotSupportedException {
  207. Prototype proto = (Prototype) super.clone();
  208. return proto;
  209. }
  210. /* 深複製 */
  211. public Object deepClone() throws IOException, ClassNotFoundException {
  212. /* 寫入當前對象的二進制流 */
  213. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  214. ObjectOutputStream oos = new ObjectOutputStream(bos);
  215. oos.writeObject(this);
  216. /* 讀出二進制流產生的新對象 */
  217. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray(
    ));
  218. ObjectInputStream ois = new ObjectInputStream(bis);
  219. return ois.readObject();
  220. }
  221. public String getString() {
  222. return string;
  223. }
  224. public void setString(String string) {
  225. this.string = string;
  226. }
  227. public SerializableObject getObj() {
  228. return obj;
  229. }
  230. public void setObj(SerializableObject obj) {
  231. this.obj = obj;
  232. }
  233. }
  234. class SerializableObject implements Serializable {
  235. private static final long serialVersionUID = 1L;
  236. }
    要實現深複製,需要採用流的形式讀入當前對象的二進制輸入,再寫出二進制數據對應的對
    象。
    B、結構模式( 7 種)
    我們接着討論設計模式,上篇文章我講完了 5 種創建型模式,這章開始,我將講下 7 種結
    構型模式:適配器模式、裝飾模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
    其中對象的適配器模式是各種模式的起源,我們看下面的圖:
    6、適配器模式
    適配器模式將某個類的接口轉換成客戶端期望的另一個接口表示,目的是消除由於接口不
    匹配所造成的類的兼容性問題。主要分爲三類:類的適配器模式、對象的適配器模式、接口
    的適配器模式。
    01、 類的適配器模式
    核心思想就是:有一個 Source 類,擁有一個方法,待適配,目標接口是 Targetable,通過
    Adapter 類,將 Source 的功能擴展到 Targetable 裏,看代碼:
    [java] view plaincopy
  237. public class Source {
  238. public void method1() {
  239. System.out.println(“this is original method!”);
  240. }
  241. }
    [java] view plaincopy
  242. public interface Targetable {
  243. /* 與原類中的方法相同 */
  244. public void method1();
  245. /* 新類的方法 */
  246. public void method2();
  247. }
    [java] view plaincopy
  248. public class Adapter extends Source implements Targetable {
  249. @Override
  250. public void method2() {
  251. System.out.println(“this is the targetable method!”);
  252. }
  253. }
    Adapter 類繼承 Source 類,實現 Targetable 接口,下面是測試類:
    [java] view plaincopy
  254. public class AdapterTest {
  255. public static void main(String[] args) {
  256. Targetable target = new Adapter();
  257. target.method1();
  258. target.method2();
  259. }
  260. }
    輸出:
    this is original method!
    this is the targetable method!
    這樣 Targetable 接口的實現類就具有了 Source 類的功能。
    02、對象的適配器模式
    基本思路和類的適配器模式相同,只是將 Adapter 類作修改,這次不繼承 Source 類,而是
    持有 Source 類的實例,以達到解決兼容性的問題。看圖:
    只需要修改 Adapter 類的源碼即可:
    [java] view plaincopy
  261. public class Wrapper implements Targetable {
  262. private Source source;
  263. public Wrapper(Source source){
  264. super();
  265. this.source = source;
  266. }
  267. @Override
  268. public void method2() {
  269. System.out.println(“this is the targetable method!”);
  270. }
  271. @Override
  272. public void method1() {
  273. source.method1();
  274. }
  275. }
    測試類:
    [java] view plaincopy
  276. public class AdapterTest {
  277. public static void main(String[] args) {
  278. Source source = new Source();
  279. Targetable target = new Wrapper(source);
  280. target.method1();
  281. target.method2();
  282. }
  283. }
    輸出與第一種一樣,只是適配的方法不同而已。
    03、接口的適配器模式
    第三種適配器模式是接口的適配器模式,接口的適配器是這樣的:有時我們寫的一個接口中
    有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比
    較浪費,因爲並不是所有的方法都是我們需要的,有時只需要某一些,此處爲了解決這個問
    題,我們引入了接口的適配器模式,藉助於一個抽象類,該抽象類實現了該接口,實現了所
    有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼
    承該抽象類,重寫我們需要的方法就行。看一下類圖:
    這個很好理解,在實際開發中,我們也常會遇到這種接口中定義了太多的方法,以致於有時
    我們在一些實現類中並不是都需要。看代碼:
    [java] view plaincopy
  284. public interface Sourceable {
  285. public void method1();
  286. public void method2();
  287. }
    抽象類 Wrapper2:
    [java] view plaincopy
  288. public abstract class Wrapper2 implements Sourceable{
  289. public void method1(){}
  290. public void method2(){}
  291. }
    [java] view plaincopy
  292. public class SourceSub1 extends Wrapper2 {
  293. public void method1(){
  294. System.out.println(“the sourceable interface’s first Sub1!”);
  295. }
  296. }
    [java] view plaincopy
  297. public class SourceSub2 extends Wrapper2 {
  298. public void method2(){
  299. System.out.println(“the sourceable interface’s second Sub2!”);
  300. }
  301. }
    [java] view plaincopy
  302. public class WrapperTest {
  303. public static void main(String[] args) {
  304. Sourceable source1 = new SourceSub1();
  305. Sourceable source2 = new SourceSub2();
  306. source1.method1();
  307. source1.method2();
  308. source2.method1();
  309. source2.method2();
  310. }
  311. }
    測試輸出:
    the sourceable interface’s first Sub1!
    the sourceable interface’s second Sub2!
    達到了我們的效果!
    講了這麼多,總結一下三種適配器模式的應用場景:
    類的適配器模式:當希望將一個類轉換成滿足另一個新接口的類時,可以使用類的適配器模
    式,創建一個新類,繼承原有的類,實現新的接口即可。
    對象的適配器模式:當希望將一個對象轉換成滿足另一個新接口的對象時,可以創建一個
    Wrapper 類,持有原類的一個實例,在 Wrapper 類的方法中,調用實例的方法就行。
    接口的適配器模式:當不希望實現一個接口中所有的方法時,可以創建一個抽象類 Wrapper,
    實現所有方法,我們寫別的類的時候,繼承抽象類即可。
    7、裝飾模式( Decorator)
    顧名思義,裝飾模式就是給一個對象增加一些新的功能,而且是動態的,要求裝飾對象和被
    裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例,關係圖如下:
    Source 類是被裝飾類, Decorator 類是一個裝飾類,可以爲 Source 類動態的添加一些功能,
    代碼如下:
    [java] view plaincopy
  312. public interface Sourceable {
  313. public void method();
  314. }
    [java] view plaincopy
  315. public class Source implements Sourceable {
  316. @Override
  317. public void method() {
  318. System.out.println(“the original method!”);
  319. }
  320. }
    [java] view plaincopy
  321. public class Decorator implements Sourceable {
  322. private Sourceable source;
  323. public Decorator(Sourceable source){
  324. super();
  325. this.source = source;
  326. }
  327. @Override
  328. public void method() {
  329. System.out.println(“before decorator!”);
  330. source.method();
  331. System.out.println(“after decorator!”);
  332. }
  333. }
    測試類:
    [java] view plaincopy
  334. public class DecoratorTest {
  335. public static void main(String[] args) {
  336. Sourceable source = new Source();
  337. Sourceable obj = new Decorator(source);
  338. obj.method();
  339. }
  340. }
    輸出:
    before decorator!
    the original method!
    after decorator!
    裝飾器模式的應用場景:
    1、需要擴展一個類的功能。
    2、動態的爲一個對象增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能
    是靜態的,不能動態增刪。)
    缺點:產生過多相似的對象,不易排錯!
    8、代理模式( Proxy)
    其實每個模式名稱就表明了該模式的作用,代理模式就是多一個代理類出來,替原對象進行
    一些操作,比如我們在租房子的時候回去找中介,爲什麼呢?因爲你對該地區房屋的信息掌
    握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的
    時候打官司,我們需要請律師,因爲律師在法律方面有專長,可以替我們進行操作,表達我
    們的想法。先來看看關係圖:
    根據上文的闡述,代理模式就比較容易的理解了,我們看下代碼:
    [java] view plaincopy
  341. public interface Sourceable {
  342. public void method();
  343. }
    [java] view plaincopy
  344. public class Source implements Sourceable {
  345. @Override
  346. public void method() {
  347. System.out.println(“the original method!”);
  348. }
  349. }
    [java] view plaincopy
  350. public class Proxy implements Sourceable {
  351. private Source source;
  352. public Proxy(){
  353. super();
  354. this.source = new Source();
  355. }
  356. @Override
  357. public void method() {
  358. before();
  359. source.method();
  360. atfer();
  361. }
  362. private void atfer() {
  363. System.out.println(“after proxy!”);
  364. }
  365. private void before() {
  366. System.out.println(“before proxy!”);
  367. }
  368. }
    測試類:
    [java] view plaincopy
  369. public class ProxyTest {
  370. public static void main(String[] args) {
  371. Sourceable source = new Proxy();
  372. source.method();
  373. }
  374. }
    輸出:
    before proxy!
    the original method!
    after proxy!
    代理模式的應用場景:
    如果已有的方法在使用的時候需要對原有的方法進行改進,此時有兩種辦法:
    1、修改原有的方法來適應。這樣違反了“對擴展開放,對修改關閉”的原則。
    2、就是採用一個代理類調用原有的方法,且對產生的結果進行控制。這種方法就是代理模
    式。
    使用代理模式,可以將功能劃分的更加清晰,有助於後期維護!
    9、外觀模式( Facade)
    外觀模式是爲了解決類與類之家的依賴關係的,像 spring 一樣,可以將類和類之間的關係
    配置到配置文件中,而外觀模式就是將他們的關係放在一個 Facade 類中,降低了類類之間
    的耦合度,該模式中沒有涉及到接口,看下類圖:(我們以一個計算機的啓動過程爲例)
    我們先看下實現類:
    [java] view plaincopy
  375. public class CPU {
  376. public void startup(){
  377. System.out.println(“cpu startup!”);
  378. }
  379. public void shutdown(){
  380. System.out.println(“cpu shutdown!”);
  381. }
  382. }
    [java] view plaincopy
  383. public class Memory {
  384. public void startup(){
  385. System.out.println(“memory startup!”);
  386. }
  387. public void shutdown(){
  388. System.out.println(“memory shutdown!”);
  389. }
  390. }
    [java] view plaincopy
  391. public class Disk {
  392. public void startup(){
  393. System.out.println(“disk startup!”);
  394. }
  395. public void shutdown(){
  396. System.out.println(“disk shutdown!”);
  397. }
  398. }
    [java] view plaincopy
  399. public class Computer {
  400. private CPU cpu;
  401. private Memory memory;
  402. private Disk disk;
  403. public Computer(){
  404. cpu = new CPU();
  405. memory = new Memory();
  406. disk = new Disk();
  407. }
  408. public void startup(){
  409. System.out.println(“start the computer!”);
  410. cpu.startup();
  411. memory.startup();
  412. disk.startup();
  413. System.out.println(“start computer finished!”);
  414. }
    19
  415. public void shutdown(){
  416. System.out.println(“begin to close the computer!”);
  417. cpu.shutdown();
  418. memory.shutdown();
  419. disk.shutdown();
  420. System.out.println(“computer closed!”);
  421. }
  422. }
    User 類如下:
    [java] view plaincopy
  423. public class User {
  424. public static void main(String[] args) {
  425. Computer computer = new Computer();
  426. computer.startup();
  427. computer.shutdown();
  428. }
  429. }
    輸出:
    start the computer!
    cpu startup!
    memory startup!
    disk startup!
    start computer finished!
    begin to close the computer!
    cpu shutdown!
    memory shutdown!
    disk shutdown!
    computer closed!
    如果我們沒有 Computer 類,那麼, CPU、 Memory、 Disk 他們之間將會相互持有實例,產
    生關係,這樣會造成嚴重的依賴,修改一個類,可能會帶來其他類的修改,這不是我們想要
    看到的,有了 Computer 類,他們之間的關係被放在了 Computer 類裏,這樣就起到了解耦
    的作用,這,就是外觀模式!
    10、橋接模式( Bridge)
    橋接模式就是把事物和其具體實現分開,使他們可以各自獨立的變化。橋接的用意是: 將抽
    象化與實現化解耦,使得二者可以獨立變化,像我們常用的 JDBC 橋 DriverManager 一樣,
    JDBC 進行連接數據庫的時候,在各個數據庫之間進行切換,基本不需要動太多的代碼,甚
    至絲毫不用動,原因就是 JDBC 提供統一接口,每個數據庫提供各自的實現,用一個叫做
    數據庫驅動的程序來橋接就行了。我們來看看關係圖:
    實現代碼:
    先定義接口:
    [java] view plaincopy
  430. public interface Sourceable {
  431. public void method();
  432. }
    分別定義兩個實現類:
    [java] view plaincopy
  433. public class SourceSub1 implements Sourceable {
  434. @Override
  435. public void method() {
  436. System.out.println(“this is the first sub!”);
  437. }
  438. }
    [java] view plaincopy
  439. public class SourceSub2 implements Sourceable {
  440. @Override
  441. public void method() {
  442. System.out.println(“this is the second sub!”);
  443. }
  444. }
    定義一個橋,持有 Sourceable 的一個實例:
    [java] view plaincopy
  445. public abstract class Bridge {
  446. private Sourceable source;
  447. public void method(){
  448. source.method();
  449. }
  450. public Sourceable getSource() {
  451. return source;
  452. }
  453. public void setSource(Sourceable source) {
  454. this.source = source;
  455. }
  456. }
    [java] view plaincopy
  457. public class MyBridge extends Bridge {
  458. public void method(){
  459. getSource().method();
  460. }
  461. }
    測試類:
    [java] view plaincopy
  462. public class BridgeTest {
  463. public static void main(String[] args) {
  464. Bridge bridge = new MyBridge();
  465. /調用第一個對象/
  466. Sourceable source1 = new SourceSub1();
  467. bridge.setSource(source1);
  468. bridge.method();
  469. /調用第二個對象/
  470. Sourceable source2 = new SourceSub2();
  471. bridge.setSource(source2);
  472. bridge.method();
  473. }
  474. }
    output:
    this is the first sub!
    this is the second sub!
    這樣,就通過對 Bridge 類的調用,實現了對接口 Sourceable 的實現類 SourceSub1 和
    SourceSub2 的調用。接下來我再畫個圖,大家就應該明白了,因爲這個圖是我們 JDBC 連
    接的原理,有數據庫學習基礎的,一結合就都懂了。
    11、組合模式( Composite)
    組合模式有時又叫部分-整體模式在處理類似樹形結構的問題時比較方便,看看關係圖:
    直接來看代碼:
    [java] view plaincopy
  475. public class TreeNode {
  476. private String name;
  477. private TreeNode parent;
  478. private Vector children = new Vector();
  479. public TreeNode(String name){
  480. this.name = name;
  481. }
  482. public String getName() {
  483. return name;
  484. }
  485. public void setName(String name) {
  486. this.name = name;
  487. }
  488. public TreeNode getParent() {
  489. return parent;
  490. }
  491. public void setParent(TreeNode parent) {
  492. this.parent = parent;
  493. }
  494. //添加孩子節點
  495. public void add(TreeNode node){
  496. children.add(node);
  497. }
  498. //刪除孩子節點
  499. public void remove(TreeNode node){
  500. children.remove(node);
  501. }
  502. //取得孩子節點
  503. public Enumeration getChildren(){
  504. return children.elements();
  505. }
  506. }
    [java] view plaincopy
  507. public class Tree {
  508. TreeNode root = null;
  509. public Tree(String name) {
  510. root = new TreeNode(name);
  511. }
  512. public static void main(String[] args) {
  513. Tree tree = new Tree(“A”);
  514. TreeNode nodeB = new TreeNode(“B”);
  515. TreeNode nodeC = new TreeNode(“C”);
  516. nodeB.add(nodeC);
  517. tree.root.add(nodeB);
  518. System.out.println(“build the tree finished!”);
  519. }
  520. }
    使用場景:將多個對象組合在一起進行操作,常用於表示樹形結構中,例如二叉樹,數等。
    12、享元模式( Flyweight)
    享元模式的主要目的是實現對象的共享,即共享池,當系統中對象多的時候可以減少內存的
    開銷,通常與工廠模式一起使用。
    FlyWeightFactory 負責創建和管理享元單元,當一個客戶端請求時,工廠需要檢查當前對象
    池中是否有符合條件的對象,如果有,就返回已經存在的對象,如果沒有,則創建一個新對
    象, FlyWeight 是超類。一提到共享池,我們很容易聯想到 Java 裏面的 JDBC 連接池,想
    想每個連接的特點,我們不難總結出:適用於作共享的一些個對象,他們有一些共有的屬性,
    就拿數據庫連接池來說, url、 driverClassName、 username、 password 及 dbname,這些
    屬性對於每個連接來說都是一樣的,所以就適合用享元模式來處理,建一個工廠類,將上述
    類似屬性作爲內部數據,其它的作爲外部數據,在方法調用時,當做參數傳進來,這樣就節
    省了空間,減少了實例的數量。
    看個例子:
    看下數據庫連接池的代碼:
    [java] view plaincopy
  521. public class ConnectionPool {
  522. private Vector pool;
  523. /公有屬性/
  524. private String url = “jdbc:mysql://localhost:3306/test”;
  525. private String username = “root”;
  526. private String password = “root”;
  527. private String driverClassName = “com.mysql.jdbc.Driver”;
  528. private int poolSize = 100;
  529. private static ConnectionPool instance = null;
  530. Connection conn = null;
  531. /構造方法,做一些初始化工作/
  532. private ConnectionPool() {
  533. pool = new Vector(poolSize);
  534. for (int i = 0; i < poolSize; i++) {
  535. try {
  536. Class.forName(driverClassName);
  537. conn = DriverManager.getConnection(url, username, password);
  538. pool.add(conn);
  539. } catch (ClassNotFoundException e) {
  540. e.printStackTrace();
  541. } catch (SQLException e) {
  542. e.printStackTrace();
  543. }
  544. }
  545. }
  546. /* 返回連接到連接池 */
  547. public synchronized void release() {
  548. pool.add(conn);
  549. }
  550. /* 返回連接池中的一個數據庫連接 */
  551. public synchronized Connection getConnection() {
  552. if (pool.size() > 0) {
  553. Connection conn = pool.get(0);
  554. pool.remove(conn);
  555. return conn;
  556. } else {
  557. return null;
  558. }
  559. }
  560. }
    通過連接池的管理,實現了數據庫連接的共享,不需要每一次都重新創建連接,節省了數據
    庫重新創建的開銷,提升了系統的性能!
    C、關係模式( 11 種)
    先來張圖,看看這 11 中模式的關係:
    第一類:通過父類與子類的關係進行實現。
    第二類:兩個類之間。
    第三類:類的狀態。
    第四類:通過中間類
    父類與子類關係
    13、策略模式( strategy)
    策略模式定義了一系列算法,並將每個算法封裝起來,使他們可以相互替換,且算法的變化
    不會影響到使用算法的客戶。需要設計一個接口,爲一系列實現類提供統一的方法,多個實
    現類實現該接口,設計一個抽象類(可有可無,屬於輔助類),提供輔助函數,關係圖如下:
    圖中 ICalculator 提供同意的方法,
    AbstractCalculator 是輔助類,提供輔助方法,接下來,依次實現下每個類:
    首先統一接口:
    [java] view plaincopy
  561. public interface ICalculator {
  562. public int calculate(String exp);
  563. }
    輔助類:
    [java] view plaincopy
  564. public abstract class AbstractCalculator {
  565. public int[] split(String exp,String opt){
  566. String array[] = exp.split(opt);
  567. int arrayInt[] = new int[2];
  568. arrayInt[0] = Integer.parseInt(array[0]);
  569. arrayInt[1] = Integer.parseInt(array[1]);
  570. return arrayInt;
  571. }
  572. }
    三個實現類:
    [java] view plaincopy
  573. public class Plus extends AbstractCalculator implements ICalculator {
  574. @Override
  575. public int calculate(String exp) {
  576. int arrayInt[] = split(exp,"\+");
  577. return arrayInt[0]+arrayInt[1];
  578. }
  579. }
    [java] view plaincopy
  580. public class Minus extends AbstractCalculator implements ICalculator {
  581. @Override
  582. public int calculate(String exp) {
  583. int arrayInt[] = split(exp,"-");
  584. return arrayInt[0]-arrayInt[1];
  585. }
  586. }
    [java] view plaincopy
  587. public class Multiply extends AbstractCalculator implements ICalculator
    {
  588. @Override
  589. public int calculate(String exp) {
  590. int arrayInt[] = split(exp,"\*");
  591. return arrayInt[0]*arrayInt[1];
  592. }
  593. }
    簡單的測試類:
    [java] view plaincopy
  594. public class StrategyTest {
  595. public static void main(String[] args) {
  596. String exp = “2+8”;
  597. ICalculator cal = new Plus();
  598. int result = cal.calculate(exp);
  599. System.out.println(result);
  600. }
  601. }
    輸出: 10
    策略模式的決定權在用戶,系統本身提供不同算法的實現,新增或者刪除算法,對各種算法
    做封裝。因此,策略模式多用在算法決策系統中,外部用戶只需要決定用哪個算法即可。
    14、模板方法模式( Template Method)
    解釋一下模板方法模式,就是指:一個抽象類中,有一個主方法,再定義 1…n 個方法,可
    以是抽象的,也可以是實際的方法,定義一個類,繼承該抽象類,重寫抽象方法,通過調用
    抽象類,實現對子類的調用,先看個關係圖:
    就是在 AbstractCalculator 類中定義一個主方法 calculate, calculate()調用 spilt()等, Plus
    和 Minus 分別繼承 AbstractCalculator 類,通過對 AbstractCalculator 的調用實現對子類的
    調用,看下面的例子:
    [java] view plaincopy
  602. public abstract class AbstractCalculator {
  603. /主方法,實現對本類其它方法的調用/
  604. public final int calculate(String exp,String opt){
  605. int array[] = split(exp,opt);
  606. return calculate(array[0],array[1]);
  607. }
  608. /被子類重寫的方法/
  609. abstract public int calculate(int num1,int num2);
  610. public int[] split(String exp,String opt){
  611. String array[] = exp.split(opt);
  612. int arrayInt[] = new int[2];
  613. arrayInt[0] = Integer.parseInt(array[0]);
  614. arrayInt[1] = Integer.parseInt(array[1]);
  615. return arrayInt;
  616. }
  617. }
    [java] view plaincopy
  618. public class Plus extends AbstractCalculator {
  619. @Override
  620. public int calculate(int num1,int num2) {
  621. return num1 + num2;
  622. }
  623. }
    測試類:
    [java] view plaincopy
  624. public class StrategyTest {
  625. public static void main(String[] args) {
  626. String exp = “8+8”;
  627. AbstractCalculator cal = new Plus();
  628. int result = cal.calculate(exp, “\+”);
  629. System.out.println(result);
  630. }
  631. }
    我跟蹤下這個小程序的執行過程:首先將 exp 和"\+"做參數,調用 AbstractCalculator 類裏
    的 calculate(String,String)方法,在 calculate(String,String)裏調用同類的 split(),之後再調
    用 calculate(int ,int)方法,從這個方法進入到子類中,執行完 return num1 + num2 後,將
    值返回到 AbstractCalculator 類,賦給 result,打印出來。正好驗證了我們開頭的思路。
    類之間的關係
    15、觀察者模式( Observer)
    包括這個模式在內的接下來的四個模式, 都是類和類之間的關係,不涉及到繼承,學的時候
    應該 記得歸納,記得本文最開始的那個圖。觀察者模式很好理解,類似於郵件訂閱和 RSS
    訂閱,當我們瀏覽一些博客或 wiki 時,經常會看到 RSS 圖標,就這的意思是,當你訂閱了
    該文章,如果後續有更新,會及時通知你。其實,簡單來講就一句話:當一個對象變化時,
    其它依賴該對象的對象都會收到通知,並且隨着變化!對象之間是一種一對多的關係。先來
    看看關係圖:
    我解釋下這些類的作用: MySubject 類就是我們的主對象, Observer1 和 Observer2 是依賴
    於 MySubject 的對象,當 MySubject 變化時, Observer1 和 Observer2 必然變化。
    AbstractSubject 類中定義着需要監控的對象列表,可以對其進行修改:增加或刪除被監控
    對象,且當 MySubject 變化時,負責通知在列表內存在的對象。我們看實現代碼:
    一個 Observer 接口:
    [java] view plaincopy
  632. public interface Observer {
  633. public void update();
  634. }
    兩個實現類:
    [java] view plaincopy
  635. public class Observer1 implements Observer {
  636. @Override
  637. public void update() {
  638. System.out.println(“observer1 has received!”);
  639. }
  640. }
    [java] view plaincopy
  641. public class Observer2 implements Observer {
  642. @Override
  643. public void update() {
  644. System.out.println(“observer2 has received!”);
  645. }
  646. }
    Subject 接口及實現類:
    [java] view plaincopy
  647. public interface Subject {
  648. /增加觀察者/
  649. public void add(Observer observer);
  650. /刪除觀察者/
  651. public void del(Observer observer);
  652. /通知所有的觀察者/
  653. public void notifyObservers();
  654. /自身的操作/
  655. public void operation();
  656. }
    [java] view plaincopy
  657. public abstract class AbstractSubject implements Subject {
  658. private Vector vector = new Vector();
  659. @Override
  660. public void add(Observer observer) {
  661. vector.add(observer);
  662. }
  663. @Override
  664. public void del(Observer observer) {
  665. vector.remove(observer);
  666. }
  667. @Override
  668. public void notifyObservers() {
  669. Enumeration enumo = vector.elements();
  670. while(enumo.hasMoreElements()){
  671. enumo.nextElement().update();
  672. }
  673. }
  674. }
    [java] view plaincopy
  675. public class MySubject extends AbstractSubject {
  676. @Override
  677. public void operation() {
  678. System.out.println(“update self!”);
  679. notifyObservers();
  680. }
  681. }
    測試類:
    [java] view plaincopy
  682. public class ObserverTest {
  683. public static void main(String[] args) {
  684. Subject sub = new MySubject();
  685. sub.add(new Observer1());
  686. sub.add(new Observer2());
  687. sub.operation();
  688. }
  689. }
    輸出:
    update self!
    observer1 has received!
    observer2 has received!
    這些東西,其實不難,只是有些抽象,不太容易整體理解,建議讀者: 根據關係圖,新
    建項目,自己寫代碼(或者參考我的代碼) ,按照總體思路走一遍,這樣才能體
    會它的思想,理解起來容易!
    16、迭代子模式( Iterator)
    顧名思義,迭代器模式就是順序訪問聚集中的對象,一般來說,集合中非常常見,如果對集
    合類比較熟悉的話,理解本模式會十分輕鬆。這句話包含兩層意思:一是需要遍歷的對象,
    即聚集對象,二是迭代器對象,用於對聚集對象進行遍歷訪問。我們看下關係圖:
    這個思路和我們常用的一模一樣, MyCollection 中定義了集合的一些操作, MyIterator 中定
    義了一系列迭代操作,且持有 Collection 實例,我們來看看實現代碼:
    兩個接口:
    [java] view plaincopy
  690. public interface Collection {
  691. public Iterator iterator();
  692. /取得集合元素/
  693. public Object get(int i);
  694. /取得集合大小/
  695. public int size();
  696. }
    [java] view plaincopy
  697. public interface Iterator {
  698. //前移
  699. public Object previous();
  700. //後移
  701. public Object next();
  702. public boolean hasNext();
  703. //取得第一個元素
  704. public Object first();
  705. }
    兩個實現:
    [java] view plaincopy
  706. public class MyCollection implements Collection {
  707. public String string[] = {“A”,“B”,“C”,“D”,“E”};
  708. @Override
  709. public Iterator iterator() {
  710. return new MyIterator(this);
  711. }
  712. @Override
  713. public Object get(int i) {
  714. return string[i];
  715. }
  716. @Override
  717. public int size() {
  718. return string.length;
  719. }
  720. }
    [java] view plaincopy
  721. public class MyIterator implements Iterator {
  722. private Collection collection;
  723. private int pos = -1;
  724. public MyIterator(Collection collection){
  725. this.collection = collection;
  726. }
  727. @Override
  728. public Object previous() {
  729. if(pos > 0){
  730. pos–;
  731. }
  732. return collection.get(pos);
  733. }
  734. @Override
  735. public Object next() {
  736. if(pos<collection.size()-1){
  737. pos++;
  738. }
  739. return collection.get(pos);
  740. }
  741. @Override
  742. public boolean hasNext() {
  743. if(pos<collection.size()-1){
  744. return true;
  745. }else{
  746. return false;
  747. }
  748. }
  749. @Override
  750. public Object first() {
  751. pos = 0;
  752. return collection.get(pos);
  753. }
  754. }
    測試類:
    [java] view plaincopy
  755. public class Test {
  756. public static void main(String[] args) {
  757. Collection collection = new MyCollection();
  758. Iterator it = collection.iterator();
  759. while(it.hasNext()){
  760. System.out.println(it.next());
  761. }
  762. }
  763. }
    輸出: A B C D E
    此處我們貌似模擬了一個集合類的過程,感覺是不是很爽?其實 JDK 中各個類也都是這些
    基本的東西,加一些設計模式,再加一些優化放到一起的,只要我們把這些東西學會了,掌
    握好了,我們也可以寫出自己的集合類,甚至框架!
    17、責任鏈模式( Chain of Responsibility)
    接下來我們將要談談責任鏈模式,有多個對象,每個對象持有對下一個對象的引用,這樣就
    會形成一條鏈,請求在這條鏈上傳遞,直到某一對象決定處理該請求。但是發出者並不清楚
    到底最終那個對象會處理該請求,所以,責任鏈模式可以實現,在隱瞞客戶端的情況下,對
    系統進行動態的調整。先看看關係圖:
    Abstracthandler 類提供了 get 和 set 方法,方便 MyHandle 類設置和修改引用對象,
    MyHandle 類是核心,實例化後生成一系列相互持有的對象,構成一條鏈。
    [java] view plaincopy
  764. public interface Handler {
  765. public void operator();
  766. }
    [java] view plaincopy
  767. public abstract class AbstractHandler {
  768. private Handler handler;
  769. public Handler getHandler() {
  770. return handler;
  771. }
  772. public void setHandler(Handler handler) {
  773. this.handler = handler;
  774. }
  775. }
    [java] view plaincopy
  776. public class MyHandler extends AbstractHandler implements Handler {
  777. private String name;
  778. public MyHandler(String name) {
  779. this.name = name;
  780. }
  781. @Override
  782. public void operator() {
  783. System.out.println(name+“deal!”);
  784. if(getHandler()!=null){
  785. getHandler().operator();
  786. }
  787. }
  788. }
    [java] view plaincopy
  789. public class Test {
  790. public static void main(String[] args) {
  791. MyHandler h1 = new MyHandler(“h1”);
  792. MyHandler h2 = new MyHandler(“h2”);
  793. MyHandler h3 = new MyHandler(“h3”);
  794. h1.setHandler(h2);
  795. h2.setHandler(h3);
  796. h1.operator();
  797. }
  798. }
    輸出:
    h1deal!
    h2deal!
    h3deal!
    此處強調一點就是,鏈接上的請求可以是一條鏈,可以是一個樹,還可以是一個環,模式本
    身不約束這個,需要我們自己去實現,同時,在一個時刻,命令只允許由一個對象傳給另一
    個對象,而不允許傳給多個對象。
    18、命令模式( Command)
    命令模式很好理解,舉個例子,司令員下令讓士兵去幹件事情,從整個事情的角度來考慮,
    司令員的作用是,發出口令,口令經過傳遞,傳到了士兵耳朵裏,士兵去執行。這個過程好
    在,三者相互解耦,任何一方都不用去依賴其他人,只需要做好自己的事兒就行,司令員要
    的是結果,不會去關注到底士兵是怎麼實現的。我們看看關係圖:
    Invoker 是調用者(司令員), Receiver 是被調用者(士兵), MyCommand 是命令,實現
    了 Command 接口,持有接收對象,看實現代碼:
    [java] view plaincopy
  799. public interface Command {
  800. public void exe();
  801. }
    [java] view plaincopy
  802. public class MyCommand implements Command {
  803. private Receiver receiver;
  804. public MyCommand(Receiver receiver) {
  805. this.receiver = receiver;
  806. }
  807. @Override
  808. public void exe() {
  809. receiver.action();
  810. }
  811. }
    [java] view plaincopy
  812. public class Receiver {
  813. public void action(){
  814. System.out.println(“command received!”);
  815. }
  816. }
    [java] view plaincopy
  817. public class Invoker {
  818. private Command command;
  819. public Invoker(Command command) {
  820. this.command = command;
  821. }
  822. public void action(){
  823. command.exe();
  824. }
  825. }
    [java] view plaincopy
  826. public class Test {
  827. public static void main(String[] args) {
  828. Receiver receiver = new Receiver();
  829. Command cmd = new MyCommand(receiver);
  830. Invoker invoker = new Invoker(cmd);
  831. invoker.action();
  832. }
  833. }
    輸出: command received!
    這個很哈理解,命令模式的目的就是達到命令的發出者和執行者之間解耦,實現請求和執行
    分開,熟悉 Struts 的同學應該知道, Struts 其實就是一種將請求和呈現分離的技術,其中必
    然涉及命令模式的思想!
    其實每個設計模式都是很重要的一種思想,看上去很熟,其實是因爲我們在學到的東西中都
    有涉及,儘管有時我們並不知道,其實在 Java 本身的設計之中處處都有體現,像 AWT、
    JDBC、集合類、 IO 管道或者是 Web 框架,裏面設計模式無處不在。因爲我們篇幅有限,
    很難講每一個設計模式都講的很詳細,不過我會盡我所能,儘量在有限的空間和篇幅內,把
    意思寫清楚了,更好讓大家明白。本章不出意外的話,應該是設計模式最後一講了,首先還
    是上一下上篇開頭的那個圖:
    本章講講第三類和第四類。
    類的狀態
    19、備忘錄模式( Memento)
    主要目的是保存一個對象的某個狀態, 以便在適當的時候恢復對象,個人覺得叫備份模式更
    形象些,通俗的講下:假設有原始類 A, A 中有各種屬性, A 可以決定需要備份的屬性,備
    忘錄類 B 是用來存儲 A 的一些內部狀態,類 C 呢,就是一個用來存儲備忘錄的,且只能存
    儲,不能修改等操作。做個圖來分析一下:
    Original 類是原始類,裏面有需要保存的屬性 value 及創建一個備忘錄類,用來保存 value
    值。 Memento 類是備忘錄類, Storage 類是存儲備忘錄的類,持有 Memento 類的實例,該
    模式很好理解。直接看源碼:
    [java] view plaincopy
  834. public class Original {
  835. private String value;
  836. public String getValue() {
  837. return value;
  838. }
  839. public void setValue(String value) {
  840. this.value = value;
  841. }
  842. public Original(String value) {
  843. this.value = value;
  844. }
  845. public Memento createMemento(){
  846. return new Memento(value);
  847. }
  848. public void restoreMemento(Memento memento){
  849. this.value = memento.getValue();
  850. }
  851. }
    [java] view plaincopy
  852. public class Memento {
  853. private String value;
  854. public Memento(String value) {
  855. this.value = value;
  856. }
  857. public String getValue() {
  858. return value;
  859. }
  860. public void setValue(String value) {
  861. this.value = value;
  862. }
  863. }
    [java] view plaincopy
  864. public class Storage {
  865. private Memento memento;
  866. public Storage(Memento memento) {
  867. this.memento = memento;
  868. }
  869. public Memento getMemento() {
  870. return memento;
  871. }
  872. public void setMemento(Memento memento) {
  873. this.memento = memento;
  874. }
  875. }
    測試類:
    [java] view plaincopy
  876. public class Test {
  877. public static void main(String[] args) {
  878. // 創建原始類
  879. Original origi = new Original(“egg”);
  880. // 創建備忘錄
  881. Storage storage = new Storage(origi.createMemento());
  882. // 修改原始類的狀態
  883. System.out.println("初始化狀態爲: " + origi.getValue());
  884. origi.setValue(“niu”);
  885. System.out.println("修改後的狀態爲: " + origi.getValue());
  886. // 回覆原始類的狀態
  887. origi.restoreMemento(storage.getMemento());
  888. System.out.println("恢復後的狀態爲: " + origi.getValue());
  889. }
  890. }
    輸出:
    初始化狀態爲: egg
    修改後的狀態爲: niu
    恢復後的狀態爲: egg
    簡單描述下:新建原始類時, value 被初始化爲 egg,後經過修改,將 value 的值置爲 niu,
    最後倒數第二行進行恢復狀態,結果成功恢復了。其實我覺得這個模式叫“備份-恢復”模式最
    形象。
    20、狀態模式( State)
    核心思想就是:當對象的狀態改變時,同時改變其行爲,很好理解!就拿 QQ 來說,有幾
    種狀態,在線、隱身、忙碌等,每個狀態對應不同的操作,而且你的好友也能看到你的狀態,
    所以,狀態模式就兩點: 1、可以通過改變狀態來獲得不同的行爲。 2、你的好友能同時看
    到你的變化。看圖:
    State 類是個狀態類, Context 類可以實現切換,我們來看看代碼:
    [java] view plaincopy
  891. package com.xtfggef.dp.state;
  892. /**
    • 狀態類的核心類
    • 2012-12-1
    • @author erqing
  893. */
  894. public class State {
  895. private String value;
  896. public String getValue() {
  897. return value;
  898. }
  899. public void setValue(String value) {
  900. this.value = value;
  901. }
  902. public void method1(){
  903. System.out.println(“execute the first opt!”);
  904. }
  905. public void method2(){
  906. System.out.println(“execute the second opt!”);
  907. }
  908. }
    [java] view plaincopy
  909. package com.xtfggef.dp.state;
  910. /**
    • 狀態模式的切換類 2012-12-1
    • @author erqing
  911. */
  912. public class Context {
  913. private State state;
  914. public Context(State state) {
  915. this.state = state;
  916. }
  917. public State getState() {
  918. return state;
  919. }
  920. public void setState(State state) {
  921. this.state = state;
  922. }
  923. public void method() {
  924. if (state.getValue().equals(“state1”)) {
  925. state.method1();
  926. } else if (state.getValue().equals(“state2”)) {
  927. state.method2();
  928. }
  929. }
  930. }
    測試類:
    [java] view plaincopy
  931. public class Test {
  932. public static void main(String[] args) {
  933. State state = new State();
  934. Context context = new Context(state);
  935. //設置第一種狀態
  936. state.setValue(“state1”);
  937. context.method();
  938. //設置第二種狀態
  939. state.setValue(“state2”);
  940. context.method();
  941. }
  942. }
    輸出:
    execute the first opt!
    execute the second opt!
    根據這個特性,狀態模式在日常開發中用的挺多的,尤其是做網站的時候,我們有時希望根
    據對象的某一屬性,區別開他們的一些功能,比如說簡單的權限控制等。
    通過中間類
    21、訪問者模式( Visitor)
    訪問者模式把數據結構和作用於結構上的操作解耦合,使得操作集合可相對自由地演化。訪
    問者模式適用於數據結構相對穩定算法又易變化的系統。因爲訪問者模式使得算法操作增加
    變得容易。若系統數據結構對象易於變化,經常有新的數據對象增加進來,則不適合使用訪
    問者模式。訪問者模式的優點是增加操作很容易,因爲增加操作意味着增加新的訪問者。訪
    問者模式將有關行爲集中到一個訪問者對象中,其改變不影響系統數據結構。其缺點就是增
    加新的數據結構很困難。 —— From 百科
    簡單來說,訪問者模式就是一種分離對象數據結構與行爲的方法,通過這種分離,可達到爲
    一個被訪問者動態添加新的操作而無需做其它的修改的效果。簡單關係圖:
    來看看原碼:一個 Visitor 類,存放要訪問的對象,
    [java] view plaincopy
  943. public interface Visitor {
  944. public void visit(Subject sub);
  945. }
    [java] view plaincopy
  946. public class MyVisitor implements Visitor {
  947. @Override
  948. public void visit(Subject sub) {
  949. System.out.println("visit the subject: "+sub.getSubject());
  950. }
  951. }
    Subject 類, accept 方法,接受將要訪問它的對象, getSubject()獲取將要被訪問的屬性,
    [java] view plaincopy
  952. public interface Subject {
  953. public void accept(Visitor visitor);
  954. public String getSubject();
  955. }
    [java] view plaincopy
  956. public class MySubject implements Subject {
  957. @Override
  958. public void accept(Visitor visitor) {
  959. visitor.visit(this);
  960. }
  961. @Override
  962. public String getSubject() {
  963. return “love”;
  964. }
  965. }
    測試:
    [java] view plaincopy
  966. public class Test {
  967. public static void main(String[] args) {
  968. Visitor visitor = new MyVisitor();
  969. Subject sub = new MySubject();
  970. sub.accept(visitor);
  971. }
  972. }
    輸出: visit the subject: love
    該模式適用場景:如果我們想爲一個現有的類增加新功能,不得不考慮幾個事情: 1、新功
    能會不會與現有功能出現兼容性問題? 2、以後會不會再需要添加? 3、如果類不允許修改
    代碼怎麼辦?面對這些問題,最好的解決方法就是使用訪問者模式,訪問者模式適用於數據
    結構相對穩定的系統,把數據結構和算法解耦,
    22、中介者模式( Mediator)
    中介者模式也是用來降低類類之間的耦合的,因爲如果類類之間有依賴關係的話,不利於功
    能的拓展和維護,因爲只要修改一個對象,其它關聯的對象都得進行修改。如果使用中介者
    模式,只需關心和 Mediator 類的關係,具體類類之間的關係及調度交給 Mediator 就行,這
    有點像 spring 容器的作用。先看看圖:
    User 類統一接口, User1 和 User2 分別是不同的對象,二者之間有關聯,如果不採用中介
    者模式,則需要二者相互持有引用,這樣二者的耦合度很高,爲了解耦,引入了 Mediator
    類,提供統一接口, MyMediator 爲其實現類,裏面持有 User1 和 User2 的實例,用來實現
    對 User1 和 User2 的控制。這樣 User1 和 User2 兩個對象相互獨立,他們只需要保持好和
    Mediator 之間的關係就行,剩下的全由 MyMediator 類來維護!基本實現:
    [java] view plaincopy
  973. public interface Mediator {
  974. public void createMediator();
  975. public void workAll();
  976. }
    [java] view plaincopy
  977. public class MyMediator implements Mediator {
  978. private User user1;
  979. private User user2;
  980. public User getUser1() {
  981. return user1;
  982. }
  983. public User getUser2() {
  984. return user2;
  985. }
  986. @Override
  987. public void createMediator() {
  988. user1 = new User1(this);
  989. user2 = new User2(this);
  990. }
  991. @Override
  992. public void workAll() {
  993. user1.work();
  994. user2.work();
  995. }
  996. }
    [java] view plaincopy
  997. public abstract class User {
  998. private Mediator mediator;
  999. public Mediator getMediator(){
  1000. return mediator;
  1001. }
  1002. public User(Mediator mediator) {
  1003. this.mediator = mediator;
  1004. }
  1005. public abstract void work();
  1006. }
    [java] view plaincopy
  1007. public class User1 extends User {
  1008. public User1(Mediator mediator){
  1009. super(mediator);
  1010. }
  1011. @Override
  1012. public void work() {
  1013. System.out.println(“user1 exe!”);
  1014. }
  1015. }
    [java] view plaincopy
  1016. public class User2 extends User {
  1017. public User2(Mediator mediator){
  1018. super(mediator);
  1019. }
  1020. @Override
  1021. public void work() {
  1022. System.out.println(“user2 exe!”);
  1023. }
  1024. }
    測試類:
    [java] view plaincopy
  1025. public class Test {
  1026. public static void main(String[] args) {
  1027. Mediator mediator = new MyMediator();
  1028. mediator.createMediator();
  1029. mediator.workAll();
  1030. }
  1031. }
    輸出:
    user1 exe!
    user2 exe!
    23、解釋器模式( Interpreter)
    解釋器模式是我們暫時的最後一講,一般主要應用在 OOP 開發中的編譯器的開發中,所以
    適用面比較窄。
    Context 類是一個上下文環境類, Plus 和 Minus 分別是用來計算的實現,代碼如下:
    [java] view plaincopy
  1032. public interface Expression {
  1033. public int interpret(Context context);
  1034. }
    [java] view plaincopy
  1035. public class Plus implements Expression {
  1036. @Override
  1037. public int interpret(Context context) {
  1038. return context.getNum1()+context.getNum2();
  1039. }
  1040. }
    [java] view plaincopy
  1041. public class Minus implements Expression {
  1042. @Override
  1043. public int interpret(Context context) {
  1044. return context.getNum1()-context.getNum2();
  1045. }
  1046. }
    [java] view plaincopy
  1047. public class Context {
  1048. private int num1;
  1049. private int num2;
  1050. public Context(int num1, int num2) {
  1051. this.num1 = num1;
  1052. this.num2 = num2;
  1053. }
  1054. public int getNum1() {
  1055. return num1;
  1056. }
  1057. public void setNum1(int num1) {
  1058. this.num1 = num1;
  1059. }
  1060. public int getNum2() {
  1061. return num2;
  1062. }
  1063. public void setNum2(int num2) {
  1064. this.num2 = num2;
  1065. }
  1066. }
    [java] view plaincopy
  1067. public class Test {
  1068. public static void main(String[] args) {
  1069. // 計算 9+2-8 的值
  1070. int result = new Minus().interpret((new Context(new Plus()
  1071. .interpret(new Context(9, 2)), 8)));
  1072. System.out.println(result);
  1073. }
  1074. }
    最後輸出正確的結果: 3。
    基本就這樣,解釋器模式用來做各種各樣的解釋器,如正則表達式等的解釋器等等!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章