各種設計模式的實現 以及其特點的詳細解析

一、軟件設計模式的概念與意義

有關軟件設計模式的定義很多,有些從模式的特點來說明,有些從模式的作用來說明,從以下兩個方面來說明。
各種設計模式的詳解與實現在最後一章節,有需要的小夥伴可以跳過前面的內容。

1. 軟件設計模式的概念

軟件設計模式(Software Design Pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。它描述了在軟件設計過程中的一些不斷重複發生的問題,以及該問題的解決方案。也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具有一定的普遍性,可以反覆使用。其目的是爲了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。

2. 學習設計模式的意義

設計模式的本質是面向對象設計原則的實際運用,是對類的封裝性、繼承性和多態性以及類的關聯關係和組合關係的充分理解。正確使用設計模式具有以下優點。

  • 可以提高程序員的思維能力、編程能力和設計能力。
  • 使程序設計更加標準化、代碼編制更加工程化,使軟件開發效率大大提高,從而縮短軟件的開發週期。
  • 使設計的代碼可重用性高、可讀性強、可靠性高、靈活性好、可維護性強。

當然,軟件設計模式只是一個引導。在具體的軟件幵發中,必須根據設計的應用系統的特點和要求來恰當選擇。對於簡單的程序開發,苛能寫一個簡單的算法要比引入某種設計模式更加容易。但對大項目的開發或者框架設計,用設計模式來組織代碼顯然更好。

二、統一建模語言簡介

統一建模語言(Unified Modeling Language,UML)是用來設計軟件藍圖的可視化建模語言,1997 年被國際對象管理組織(OMG)採納爲面向對象的建模語言的國際標準。
統一建模語言能爲軟件開發的所有階段提供模型化和可視化支持。而且融入了軟件工程領域的新思想、新方法和新技術,使軟件設計人員溝通更簡明,進一步縮短了設計時間,減少開發成本。它的應用領域很寬,不僅適合於一般系統的開發,而且適合於並行與分佈式系統的建模。

本教程主要介紹軟件中經常用到的類圖,以及類之間的關係。另外,在實驗部分將簡單介紹 UML 建模工具的使用方法,當前業界使用最廣泛的是 Rational Rose。使用 Umlet 的人也很多,它是一個輕量級的開源 UML 建模工具,簡單實用,常用於小型軟件系統的開發與設計。

1. 類

類(Class)是指具有相同屬性、方法和關係的對象的抽象,它封裝了數據和行爲,是面向對象程序設計(OOP)的基礎,具有封裝性、繼承性和多態性等三大特性。在 UML 中,類使用包含類名、屬性和操作且帶有分隔線的矩形來表示。
(1) 類名(Name)是一個字符串,例如,Student。

[可見性]屬性名:類型[=默認值]

例如:-name:String
注意:“可見性”表示該屬性對類外的元素是否可見,包括公有(Public)、私有(Private)、受保護(Protected)和朋友(Friendly)4 種,在類圖中分別用符號+、-、#、~表示。

[可見性]名稱(參數列表)[:返回類型]

例如:+display():void。
圖 1 所示是學生類的 UML 表示。

Student 類
圖1 Student 類

2. 接口

接口(Interface)是一種特殊的類,它具有類的結構但不可被實例化,只可以被子類實現。它包含抽象操作,但不包含屬性。它描述了類或組件對外可見的動作。在 UML 中,接口使用一個帶有名稱的小圓圈來進行表示。
圖 2 所示是圖形類接口的 UMDL 表示。

img

3. 類圖

類圖(ClassDiagram)是用來顯示系統中的類、接口、協作以及它們之間的靜態結構和關係的一種靜態模型。它主要用於描述軟件系統的結構化設計,幫助人們簡化對軟件系統的理解,它是系統分析與設計階段的重要產物,也是系統編碼與測試的重要模型依據。
類圖中的類可以通過某種編程 語言直接實現。類圖在軟件系統開發的整個生命週期都是有效的,它是面向對象系統的建模中最常見的圖。圖 3 所示是“計算長方形和圓形的周長與面積”的類圖,圖形接口有計算面積和周長的抽象方法,長方形和圓形實現這兩個方法供訪問類調用。

“計算長方形和圓形的周長與面積”的類圖

類之間的關係

在軟件系統中,類不是孤立存在的,類與類之間存在各種關係。根據類與類之間的耦合度從弱到強排列,UML 中的類圖有以下幾種關係:依賴關係、關聯關係、聚合關係、組合關係、泛化關係和實現關係。其中泛化和實現的耦合度相等,它們是最強的。

1. 依賴關係

依賴(Dependency)關係是一種使用關係,它是對象之間耦合度最弱的一種關聯方式,是臨時性的關聯。在代碼中,某個類的方法通過局部變量、方法的參數或者對靜態方法的調用來訪問另一個類(被依賴類)中的某些方法來完成一些職責。
在 UML 類圖中,依賴關係使用帶箭頭的虛線來表示,箭頭從使用類指向被依賴的類。圖 4 所示是人與手機的關係圖,人通過手機的語音傳送方法打電話。

依賴關係的實例

2. 關聯關係

關聯(Association)關係是對象之間的一種引用關係,用於表示一類對象與另一類對象之間的聯繫,如老師和學生、師傅和徒弟、丈夫和妻子等。關聯關係是類與類之間最常用的一種關係,分爲一般關聯關係、聚合關係和組合關係。我們先介紹一般關聯。
關聯可以是雙向的,也可以是單向的。在 UML 類圖中,雙向的關聯可以用帶兩個箭頭或者沒有箭頭的實線來表示,單向的關聯用帶一個箭頭的實線來表示,箭頭從使用類指向被關聯的類。也可以在關聯線的兩端標註角色名,代表兩種不同的角色。

關聯關係的實例
圖5 關聯關係的實例

3. 聚合關係

聚合(Aggregation)關係是關聯關係的一種,是強關聯關係,是整體和部分之間的關係,是 has-a 的關係。
聚合關係也是通過成員對象來實現的,其中成員對象是整體對象的一部分,但是成員對象可以脫離整體對象而獨立存在。例如,學校與老師的關係,學校包含老師,但如果學校停辦了,老師依然存在。

聚合關係的實例
圖6 聚合關係的實例

4.組合關係

組合(Composition)關係也是關聯關係的一種,也表示類之間的整體與部分的關係,但它是一種更強烈的聚合關係,是 contains-a 關係。
在組合關係中,整體對象可以控制部分對象的生命週期,一旦整體對象不存在,部分對象也將不存在,部分對象不能脫離整體對象而存在。例如,頭和嘴的關係,沒有了頭,嘴也就不存在了。

組合關係的實例
圖7 組合關係的實例

5.泛化關係

泛化(Generalization)關係是對象之間耦合度最大的一種關係,表示一般與特殊的關係,是父類與子類之間的關係,是一種繼承關係,是 is-a 的關係。
在 UML 類圖中,泛化關係用帶空心三角箭頭的實線來表示,箭頭從子類指向父類。在代碼實現時,使用面向對象的繼承機制來實現泛化關係。例如,Student 類和 Teacher 類都是 Person 類的子類,其類圖如圖 8 所示。
泛化關係的實例

6.實現關係

實現(Realization)關係是接口與實現類之間的關係。在這種關係中,類實現了接口,類中的操作實現了接口中所聲明的所有的抽象操作。
在 UML 類圖中,實現關係使用帶空心三角箭頭的虛線來表示,箭頭從實現類指向接口。例如,汽車和船實現了交通工具,其類圖如圖 9 所示。

實現關係的實例

三、設計模式詳解

1.單例模式

在有些系統中,爲了節省內存資源、保證數據內容的一致性,對某些類要求只能創建一個實例,這就是所謂的單例模式。

單例模式的定義與特點

單例(Singleton)模式的定義:指一個類只有一個實例,且該類能自行創建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示內容的不一致等錯誤。
在計算機系統中,還有 Windows 的回收站、操作系統中的文件系統、多線程中的線程池、顯卡的驅動程序對象、打印機的後臺處理服務、應用程序的日誌對象、數據庫的連接池、網站的計數器、Web 應用的配置對象、應用程序中的對話框、系統中的緩存等常常被設計成單例。

  1. 單例類只有一個實例對象;
  2. 該單例對象必須由單例類自行創建;
  3. 單例類對外提供一個訪問該單例的全局訪問點;

單例模式的結構與實現

單例模式的主要角色如下。

  • 單例類:包含一個實例且能自行創建這個實例的類。
  • 訪問類:使用單例的類。

單例模式的結構圖

第 1 種:枚舉實現
/**
 * 枚舉形式的單例實現,餓漢式,線程安全
 */

public enum EnumIvoryTower {

  INSTANCE;

  private String test = "111";

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }

  public String getText(){
    return test;
  }
}

第 2 種:靜態內部類實現
/**
  * 靜態內部類實現,懶漢式,線程安全,適用於所有的JDK版本
 *  原理:內部類初始化時間會在getInstance()之後,所以不需要在構造方法中所其他額外的線程安全處理
 *  虛擬機會保證一個類的構造器<clinit>()方法在多線程環境中被正確地加載,同步,如果多個線程同時去初始化一個類,那麼只有一個線程去執行這個類的
 *  構造器<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。
 */
public final class InitializingOnDemandHolderIdiom {

  /**
   * Private constructor.
   */
  private InitializingOnDemandHolderIdiom() {
    System.out.println("我被創建了");
  }

  /**
   * @return Singleton instance
   */
  public static InitializingOnDemandHolderIdiom getInstance() {
    System.out.println("getInstance被調用了");
    return HelperHolder.INSTANCE;
  }

  /**
   * Provides the lazy-loaded Singleton instance.
   */
  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }
}

第 3 種:靜態變量實現
/**
 * 靜態變量,餓漢式,線程安全
 */
public final class IvoryTower {

  /**
   * Private constructor so nobody can instantiate the class.
   */
  private IvoryTower() {
    System.out.println("我被創建了");
  }

  /**
   * Static to class instance of the class.
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * To be called by user to obtain instance of the class.
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    System.out.println("getInstance方法被調用了");
    return INSTANCE;
  }
}

第 4 種:雙重檢查鎖
/**
 * 雙重檢查鎖,懶漢式,線程安全
 */
public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  private static boolean flag = true;

  /**
   * 私有構造函數,避免直接被創建
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (flag) {
      flag = false;
    } else {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *  公有創建函數
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {

    ThreadSafeDoubleCheckLocking result = instance;
     //從這兒開始
    //判斷是否被創建過了,如果被創建就直接返回
    if (result == null) {
      //雖然沒有被初始化,但是無法保證此時沒有其他線程在初始化對象,所以加一個類鎖
      synchronized (ThreadSafeDoubleCheckLocking.class) {
          //到這兒結束
        //再次確認是否被初始化了,這主要是爲了防止如果這個線程之前無法獲得鎖有其他線程做了初始化的動作,那		  //麼在獲得鎖之前獲取的對象可能已經發生了變化
        result = instance;
        if (result == null) {
          //這裏的判斷纔是真正正確的,此時不會有其他線程進入這塊代碼塊
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

2.工廠方法模式

模式的定義與特點

工廠方法(FactoryMethod)模式的定義:定義一個創建產品對象的工廠接口,將產品對象的實際創建工作推遲到具體子工廠類當中。這滿足創建型模式中所要求的“創建與使用相分離”的特點。
如果要創建的產品不多,只要一個工廠類就可以完成,這種模式叫“簡單工廠模式”,它不屬於 GoF 的 23 種經典設計模式,它的缺點是增加新產品時會違背“開閉原則”。

本節介紹的“工廠方法模式”是對簡單工廠模式的進一步抽象化,其好處是可以使系統在不修改原來代碼的情況下引進新的產品,即滿足開閉原則。
工廠方法模式的主要優點有:
用戶只需要知道具體工廠的名稱就可得到所要的產品,無須知道產品的具體創建過程;在系統增加新的產品時 只需要添加具體產品類和對應的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則;

其缺點是:每增加一個產品就要增加一個具體產品類和一個對應的具體工廠類,這增加了系統的複雜度。

模式的結構與實現

工廠方法模式由抽象工廠、具體工廠、抽象產品和具體產品等4個要素構成。本節來分析其基本結構和實現方法。

工廠方法模式的主要角色如下。

  1. 模式的結構抽象工廠(Abstract Factory):提供了創建產品的接口,調用者通過它訪問具體工廠的工廠方法 newProduct() 來創建產品。
  2. 具體工廠(ConcreteFactory):主要是實現抽象工廠中的抽象方法,完成具體產品的創建。
  3. 抽象產品(Product):定義了產品的規範,描述了產品的主要特性和功能。
  4. 具體產品(ConcreteProduct):實現了抽象產品角色所定義的接口,由具體工廠來創建,它同具體工廠之間一一對應。

工廠方法模式的結構圖

/**
 *
 * 鐵匠接口,鐵匠打造武器就相當於工廠生產產品
 *
 */
public interface Blacksmith {

  //打造武器
  Weapon manufactureWeapon(WeaponType weaponType);

}
/**
 * 武器接口
 */
public interface Weapon {

  //獲得武器的類型
  WeaponType getWeaponType();

}
/**
 * 
 * 武器類型的枚舉
 *
 */
public enum WeaponType {

  SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), UNDEFINED("");

  private String title;

  WeaponType(String title) {
    this.title = title;
  }

  @Override
  public String toString() {
    return title;
  }
}
/**
 * 
 * 精靈鐵匠,打造精靈族武器
 * 
 */
public class ElfBlacksmith implements Blacksmith {

  @Override
  public Weapon manufactureWeapon(WeaponType weaponType) {
    return new ElfWeapon(weaponType);
  }

}
/**
 * 精靈族武器
 */
public class ElfWeapon implements Weapon {

  private WeaponType weaponType;

  public ElfWeapon(WeaponType weaponType) {
    this.weaponType = weaponType;
  }

  @Override
  public String toString() {
    return "Elven " + weaponType;
  }

  @Override
  public WeaponType getWeaponType() {
    return weaponType;
  }
}

/**
 * 
 * 獸人鐵匠,打造獸人族武器
 * 
 */
public class OrcBlacksmith implements Blacksmith {

  @Override
  public Weapon manufactureWeapon(WeaponType weaponType) {
    return new OrcWeapon(weaponType);
  }
}

/**
 * 獸人族武器
 */
public class OrcWeapon implements Weapon {

  private WeaponType weaponType;

  public OrcWeapon(WeaponType weaponType) {
    this.weaponType = weaponType;
  }

  @Override
  public String toString() {
    return "Orcish " + weaponType;
  }

  @Override
  public WeaponType getWeaponType() {
    return weaponType;
  }
}

3.抽象工廠模式

工廠方法模式只考慮生產同等級的產品,但是在現實生活中許多工廠是綜合型的工廠,能生產多等級(種類) 的產品,如農場裏既養動物又種植物,電器廠既生產電視機又生產洗衣機或空調,大學既有軟件專業又有生物專業等。

模式的定義與特點

抽象工廠(AbstractFactory)模式的定義:是一種爲訪問類提供一個創建一組相關或相互依賴對象的接口,且訪問類無須指定所要產品的具體類就能得到同族的不同等級的產品的模式結構。

抽象工廠模式是工廠方法模式的升級版本,工廠方法模式只生產一個等級的產品,而抽象工廠模式可生產多個等級的產品。

使用抽象工廠模式一般要滿足以下條件。

  • 可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
  • 當增加一個新的產品族時不需要修改原代碼,滿足開閉原則。

抽象工廠模式除了具有工廠方法模式的優點外,其他主要優點如下。

  • 可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
  • 當增加一個新的產品族時不需要修改原代碼,滿足開閉原則。

其缺點是:當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。

模式的結構與實現

抽象工廠模式同工廠方法模式一樣,也是由抽象工廠、具體工廠、抽象產品和具體產品等 4 個要素構成,但抽象工廠中方法個數不同,抽象產品的個數也不同。現在我們來分析其基本結構和實現方法。

抽象工廠模式的主要角色如下。

  1. 抽象工廠(Abstract Factory):提供了創建產品的接口,它包含多個創建產品的方法 newProduct(),可以創建多個不同等級的產品。
  2. 具體工廠(Concrete Factory):主要是實現抽象工廠中的多個抽象方法,完成具體產品的創建。
  3. 抽象產品(Product):定義了產品的規範,描述了產品的主要特性和功能,抽象工廠模式有多個抽象產品。
  4. 具體產品(ConcreteProduct):實現了抽象產品角色所定義的接口,由具體工廠來創建,它 同具體工廠之間是多對一的關係。

抽象工廠模式的結構圖

4.建造者模式

在軟件開發過程中有時需要創建一個複雜的對象,這個複雜對象通常由多個子部件按一定的步驟組合而成。例如,計算機是由 OPU、主板、內存、硬盤、顯卡、機箱、顯示器、鍵盤、鼠標等部件組裝而成的,採購員不可能自己去組裝計算機,而是將計算機的配置要求告訴計算機銷售公司,計算機銷售公司安排技術人員去組裝計算機,然後再交給要買計算機的採購員。

生活中這樣的例子很多,如遊戲中的不同角色,其性別、個性、能力、臉型、體型、服裝、髮型等特性都有所差異;還有汽車中的方向盤、發動機、車架、輪胎等部件也多種多樣;每封電子郵件的發件人、收件人、主題、內容、附件等內容也各不相同。

以上所有這些產品都是由多個部件構成的,各個部件可以靈活選擇,但其創建步驟都大同小異。這類產品的創建無法用前面介紹的工廠模式描述,只有建造者模式可以很好地描述該類產品的創建。

模式的定義與特點

建造者(Builder)模式的定義:指將一個複雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示,這樣的設計模式被稱爲建造者模式。它是將一個複雜的對象分解爲多個簡單的對象,然後一步一步構建而成。它將變與不變相分離,即產品的組成部分是不變的,但每一部分是可以靈活選擇的。

該模式的主要優點如下:

  1. 各個具體的建造者相互獨立,有利於系統的擴展。
  2. 客戶端不必知道產品內部組成的細節,便於控制細節風險。

其缺點如下:

  1. 產品的組成部分必須相同,這限制了其使用範圍。
  2. 如果產品的內部變化複雜,該模式會增加很多的建造者類。

建造者(Builder)模式和工廠模式的關注點不同:建造者模式注重零部件的組裝過程,而工廠方法模式更注重零部件的創建過程,但兩者可以結合使用。

模式的結構與實現

建造者(Builder)模式由產品、抽象建造者、具體建造者、指揮者等 4 個要素構成,現在我們來分析其基本結構和實現方法。

建造者(Builder)模式的主要角色如下。

  1. 產品角色(Product):它是包含多個組成部件的複雜對象,由具體建造者來創建其各個滅部件。
  2. 抽象建造者(Builder):它是一個包含創建產品各個子部件的抽象方法的接口,通常還包含一個返回複雜產品的方法 getResult()。
  3. 具體建造者(Concrete Builder):實現 Builder 接口,完成複雜產品的各個部件的具體創建方法。
  4. 指揮者(Director):它調用建造者對象中的部件構造與裝配方法完成複雜對象的創建,在指揮者中不涉及具體產品的信息

建造者模式的結構圖

5.代理模式

在有些情況下,一個客戶不能或者不想直接訪問另一個對象,這時需要找一箇中介幫忙完成某項任務,這個中介就是代理對象。例如,購買火車票不一定要去火車站買,可以通過 12306 網站或者去火車票代售點買。又如找女朋友、找保姆、找工作等都可以通過找中介完成。

在軟件設計中,使用代理模式的例子也很多,例如,要訪問的遠程對象比較大(如視頻或大圖像等),其下載要花很多時間。還有因爲安全原因需要屏蔽客戶端直接訪問真實對象,如某單位的內部數據庫等。

代理模式的定義與特點

代理模式的定義:由於某些原因需要給某對象提供一個代理以控制對該對象的訪問。這時,訪問對象不適合或者不能直接引用目標對象,代理對象作爲訪問對象和目標對象之間的中介。

代理模式的主要優點有:

  • 代理模式在客戶端與目標對象之間起到一箇中介作用和保護目標對象的作用;
  • 代理對象可以擴展目標對象的功能;
  • 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度;

其主要缺點是:

  • 在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢;
  • 增加了系統的複雜度;

代理模式的結構與實現

代理模式的結構比較簡單,主要是通過定義一個繼承抽象主題的代理來包含真實主題,從而實現對真實主題的訪問,下面來分析其基本結構和實現方法。

代理模式的主要角色如下。

  1. 抽象主題(Subject)類:通過接口或抽象類聲明真實主題和代理對象實現的業務方法。
  2. 真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理對象所代表的真實對象,是最終要引用的對象。
  3. 代理(Proxy)類:提供了與真實主題相同的接口,其內部含有對真實主題的引用,它可以訪問、控制或擴展真實主題的功能。

代理模式的結構圖

6.適配器模式

在現實生活中,經常出現兩個對象因接口不兼容而不能在一起工作的實例,這時需要第三者進行適配。例如,講中文的人同講英文的人對話時需要一個翻譯,用直流電的筆記本電腦接交流電源時需要一個電源適配器,用計算機訪問照相機的 SD 內存卡時需要一個讀卡器等。

在軟件設計中也可能出現:需要開發的具有某種業務功能的組件在現有的組件庫中已經存在,但它們與當前系統的接口規範不兼容,如果重新開發這些組件成本又很高,這時用適配器模式能很好地解決這些問題。

模式的定義與特點

適配器模式(Adapter)的定義如下:將一個類的接口轉換成客戶希望的另外一個接口,使得原本由於接口不兼容而不能一起工作的那些類能一起工作。適配器模式分爲類結構型模式和對象結構型模式兩種,前者類之間的耦合度比後者高,且要求程序員瞭解現有組件庫中的相關組件的內部結構,所以應用相對較少些。

該模式的主要優點如下。

  • 客戶端通過適配器可以透明地調用目標接口。

  • 複用了現存的類,程序員不需要修改原有代碼而重用現有的適配者類。

  • 將目標類和適配者類解耦,解決了目標類和適配者類接口不一致的問題。

    其缺點是:對類適配器來說,更換適配器的實現過程比較複雜。

模式的結構與實現

類適配器模式可採用多重繼承方式實現,如 C++ 可定義一個適配器類來同時繼承當前系統的業務接口和現有組件庫中已經存在的組件接口;Java 不支持多繼承,但可以定義一個適配器類來實現當前系統的業務接口,同時又繼承現有組件庫中已經存在的組件。

對象適配器模式可釆用將現有組件庫中已經實現的組件引入適配器類中,該類同時實現當前系統的業務接口。現在來介紹它們的基本結構。

適配器模式(Adapter)包含以下主要角色。

  1. 目標(Target)接口:當前系統業務所期待的接口,它可以是抽象類或接口。
  2. 適配者(Adaptee)類:它是被訪問和適配的現存組件庫中的組件接口。
  3. 適配器(Adapter)類:它是一個轉換器,通過繼承或引用適配者的對象,把適配者接口轉換成目標接口,讓客戶按目標接口的格式訪問適配者。

類適配器模式的結構圖

7.裝飾模式

在現實生活中,常常需要對現有產品增加新的功能或美化其外觀,如房子裝修、相片加相框等。在軟件開發過程中,有時想用一些現存的組件。這些組件可能只是完成了一些核心功能。但在不改變其結構的情況下,可以動態地擴展其功能。所有這些都可以釆用裝飾模式來實現。

裝飾模式的定義與特點

裝飾(Decorator)模式的定義:指在不改變現有對象結構的情況下,動態地給該對象增加一些職責(即增加其額外功能)的模式,它屬於對象結構型模式。

裝飾(Decorator)模式的主要優點有:

  • 採用裝飾模式擴展對象的功能比採用繼承方式更加靈活。
  • 可以設計出多個不同的具體裝飾類,創造出多個不同行爲的組合。

其主要缺點是:裝飾模式增加了許多子類,如果過度使用會使程序變得很複雜。

裝飾模式的結構與實現

通常情況下,擴展一個類的功能會使用繼承方式來實現。但繼承具有靜態特徵,耦合度高,並且隨着擴展功能的增多,子類會很膨脹。如果使用組合關係來創建一個包裝對象(即裝飾對象)來包裹真實對象,並在保持真實對象的類結構不變的前提下,爲其提供額外的功能,這就是裝飾模式的目標。下面來分析其基本結構和實現方法。

裝飾模式主要包含以下角色。

  1. 抽象構件(Component)角色:定義一個抽象接口以規範準備接收附加責任的對象。
  2. 具體構件(Concrete Component)角色:實現抽象構件,通過裝飾角色爲其添加一些職責。
  3. 抽象裝飾(Decorator)角色:繼承抽象構件,幷包含具體構件的實例,可以通過其子類擴展具體構件的功能。
  4. 具體裝飾(ConcreteDecorator)角色:實現抽象裝飾的相關方法,並給具體構件對象添加附加的責任。

裝飾模式的結構圖

public class CustomFilterInputStream extends FilterInputStream {
    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected CustomFilterInputStream(InputStream in) {
        super(in);
    }

    public synchronized int read(byte b[], int off, int len)
            throws IOException {
        System.out.println("只是加點裝飾而已");
        return in.read(b, off, len);
    }

}
		File file = new File("d:\\python.txt");
        FileInputStream fis = new FileInputStream(file);
        CustomFilterInputStream bis = new CustomFilterInputStream(fis);

8.模板方法模式

在面向對象程序設計過程中,程序員常常會遇到這種情況:設計一個系統時知道了算法所需的關鍵步驟,而且確定了這些步驟的執行順序,但某些步驟的具體實現還未知,或者說某些步驟的實現與具體的環境相關。

例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對銀行工作人員進行評分等,其中取號、排隊和對銀行工作人員進行評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實現。

這樣的例子在生活中還有很多,例如,一個人每天會起牀、吃飯、做事、睡覺等,其中“做事”的內容每天可能不同。我們把這些規定了流程或格式的實例定義成模板,允許使用者根據自己的需求去更新它,例如,簡歷模板、論文模板、Word 中模板文件等。

以下介紹的模板方法模式將解決以上類似的問題。

模式的定義與特點

模板方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行爲型模式。

該模式的主要優點如下。

  1. 它封裝了不變部分,擴展可變部分。它把認爲是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
  2. 它在父類中提取了公共的部分代碼,便於代碼複用。
  3. 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。

該模式的主要缺點如下。

  1. 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
  2. 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。

模式的結構與實現

模板方法模式需要注意抽象類與具體子類之間的協作。它用到了虛函數的多態性技術以及“不用調用我,讓我來調用你”的反向控制技術。現在來介紹它們的基本結構。

(1) 抽象類(Abstract Class):負責給出一個算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。這些方法的定義如下。

① 模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法。

② 基本方法:是整個算法中的一個步驟,包含以下幾種類型。

  • 抽象方法:在抽象類中申明,由具體子類實現。
  • 具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
  • 鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。

(2) 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的一個組成步驟。

模板方法模式的結構圖

9.策略模式

在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。

在軟件開發中也常常遇到類似的情況,當實現某一個功能存在多種算法或者策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能,如數據排序策略有冒泡排序、選擇排序、插入排序、二叉樹排序等。

如果使用多重條件轉移語句實現(即硬編碼),不但使條件語句變得很複雜,而且增加、刪除或更換算法要修改原代碼,不易維護,違背開閉原則。如果採用策略模式就能很好解決該問題。

策略模式的定義與特點

策略(Strategy)模式的定義:該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。策略模式屬於對象行爲模式,它通過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不同的對象對這些算法進行管理。

策略模式的主要優點如下。

  1. 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句。
  2. 策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裏面,從而避免重複的代碼。
  3. 策略模式可以提供相同行爲的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
  4. 策略模式提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
  5. 策略模式把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。

其主要缺點如下。

  1. 客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
  2. 策略模式造成很多的策略類。

策略模式的結構與實現

策略模式是準備一組算法,並將這組算法封裝到一系列的策略類裏面,作爲一個抽象策略類的子類。策略模式的重心不是如何實現算法,而是如何組織這些算法,從而讓程序結構更加靈活,具有更好的維護性和擴展性,現在我們來分析其基本結構和實現方法。

策略模式的主要角色如下。

  1. 抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現。
  2. 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現。
  3. 環境(Context)類:持有一個策略類的引用,最終給客戶端調用。

策略模式的結構圖

10.責任鏈模式

在現實生活中,常常會出現這樣的事例:一個請求有多個對象可以處理,但每個對象的處理條件或權限不同。例如,公司員工請假,可批假的領導有部門負責人、副總經理、總經理等,但每個領導能批准的天數不同,員工必須根據自己要請假的天數去找不同的領導簽名,也就是說員工必須記住每個領導的姓名、電話和地址等信息,這增加了難度。這樣的例子還有很多,如找領導出差報銷、生活中的“擊鼓傳花”遊戲等。

在計算機軟硬件中也有相關例子,如總線網中數據報傳送,每臺計算機根據目標地址是否同自己的地址相同來決定是否接收;還有異常處理中,處理程序根據異常的類型決定自己是否處理該異常;還有 Struts2 的攔截器、JSPServlet 的 Filter 等,所有這些,如果用責任鏈模式都能很好解決。

模式的定義與特點

責任鏈(Chain of Responsibility)模式的定義:爲了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它爲止。

責任鏈模式是一種對象行爲型模式,其主要優點如下。

  1. 降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無須擁有對方的明確信息。

  2. 增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。

  3. 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。

  4. 責任鏈簡化了對象之間的連接。每個對象只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。

  5. 責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任範圍,符合類的單一職責原則。

其主要缺點如下。

  1. 不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。

  2. 對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響。

  3. 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。

模式的結構與實現

通常情況下,可以通過數據鏈表來實現職責鏈模式的數據結構

職責鏈模式主要包含以下角色。

  1. 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個後繼連接。

  2. 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。

  3. 客戶類(Client)角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。

    責任鏈模式的結構圖

責任鏈

11.觀察者模式

在現實世界中,許多對象並不是獨立存在的,其中一個對象的行爲發生改變可能會導致一個或者多個其他對象的行爲也發生改變。例如,某種商品的物價上漲時會導致部分商家高興,而消費者傷心;還有,當我們開車到交叉路口時,遇到紅燈會停,遇到綠燈會行。這樣的例子還有很多,例如,股票價格與股民、微信公衆號與微信用戶、氣象局的天氣預報與聽衆、小偷與警察等。

在軟件世界也是這樣,例如,Excel 中的數據與折線圖、餅狀圖、柱狀圖之間的關係;MVC 模式中的模型與視圖的關係;事件模型中的事件源與事件處理者。所有這些,如果用觀察者模式來實現就非常方便。

模式的定義與特點

觀察者(Observer)模式的定義:指多個對象間存在一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發佈-訂閱模式、模型-視圖模式,它是對象行爲型模式。

觀察者模式是一種對象行爲型模式,其主要優點如下。

  1. 降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。
  2. 目標與觀察者之間建立了一套觸發機制。

它的主要缺點如下。

  1. 目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現循環引用。
  2. 當觀察者對象很多時,通知的發佈會花費很多時間,影響程序的效率。

模式的結構與實現

實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。

觀察者模式的主要角色如下。

  1. 抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
  2. 具體主題(Concrete Subject)角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有註冊過的觀察者對象。
  3. 抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
  4. 具體觀察者(Concrete Observer)角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。

觀察者模式的結構圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章