常用的結構型設計模式(C#舉例)


概述

  1. 代理模式:代理模式在不改變原始類接口的條件下,爲原始類定義一個代理類,主要目的是控制訪問,而非加強功能,這是它跟裝飾器模式最大的不同。
  2. 裝飾器模式:裝飾者模式在不改變原始類接口的情況下,對原始類功能進行增強,並且支持多個裝飾器的嵌套使用。
  3. 適配器模式:適配器模式是一種事後的補救策略。適配器提供跟原始類不同的接口,而代理模式、裝飾器模式提供的都是跟原始類相同的接口。

代理模式

它在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能。

  1. 一般情況下,讓代理類和原始類實現同樣的接口
  2. 如果原始類並沒有定義接口,並且原始類代碼並不是我們開發維護的,可以通過讓代理類繼承原始類的方法來實現代理模式
  3. 常用在業務系統中開發非功能性需求,比如:監控、統計、鑑權、限流、事務、冪等、日誌等
  4. 將附加功能與業務功能解耦,放到代理類統一處理,方便讓程序員只關注業務方面的開發
// author: [email protected]
public interface IRunner{
// 接口也可以替換成抽象類
  void run();
}
public class Marathon : IRunner {
  public void run() { //... }
}
public class CrossCountryProxy : IRunner {
  private IRunner runner;
  public CrossCountryProxy(IRunner runner) {
    this.runner = runner;
  }
  
  public void run() {
    // 新添加的代理邏輯
    runner.run();
    // 新添加的代理邏輯
  }
}

裝飾器模式

裝飾器模式主要解決繼承關係過於複雜的問題,通過組合來替代繼承。它主要的作用是給原始類添加增強功能,這也是判斷是否該用裝飾器模式的一個重要的依據。
從代碼看,兩種模式完全一樣,只是其設計的側重點不同,簡單來講:

  1. 代理:偏重因自己無法完成或自己無需關心,需要他人干涉事件流程。
  2. 裝飾:偏重對原對象功能的擴展,擴展後的對象仍是是對象本身。
// author: [email protected]
public interface IRunner{
// 接口也可以替換成抽象類
  void run();
}
public class Marathon : IRunner {
  public void run() { //... }
}
public class CrossCountryDecorator : IRunner {
  private IRunner runner;
  public CrossCountryDecorator(IRunner runner) {
    this.runner = runner;
  }
  
  public void run() {
    // 功能增強代碼
    runner.run();
    // 功能增強代碼
  }
}

適配器模式

用來做適配的,它將不兼容的接口轉換爲可兼容的接口,常用有兩種實現方式:

  1. 類適配器:使用繼承關係來實現
  2. 對象適配器:使用組合關係來實現

判斷使用哪個的標準一般有兩個,一個是 Adaptee 接口的個數,另一個是 Adaptee 和 ITarget 的契合程度

  1. 如果 Adaptee 接口並不多,那兩種實現方式都可以。
  2. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定義大部分都相同,那我們推薦使用類適配器,因爲 Adaptor 複用父類 Adaptee 的接口,比起對象適配器的實現方式,Adaptor 的代碼量要少一些。
  3. 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定義大部分都不相同,那我們推薦使用對象適配器,因爲組合結構相對於繼承更加靈活。

使用適配器模式的場景一般有以下5種情況:

  1. 封裝有缺陷的接口設計
  2. 統一多個類的接口設計
  3. 替換依賴的外部系統
  4. 兼容老版本接口
  5. 適配不同格式的數據
// author: [email protected]
public interface IRunner {
  void walk();
  void run();
}

public class CrossCountry {
  public void run(){}
  public void eat(){}
}

// 類適配器: 基於繼承
public class SportsAdaptor : CrossCountry, IRunner {
  public void walk() {
    super.eat();
    //其它行爲
  }
  // 跟對象適配器最大的不同點:
  // 這裏run可以不實現,直接繼承自CrossCountry
}

// 對象適配器:基於組合
public class SportsAdaptor : IRunner  {
  private CrossCountry crossCountry;
  
  public Adaptor(CrossCountry crossCountry) {
    this.crossCountry = crossCountry;
  }
  
  public void walk() {
    this.crossCountry.eat(); //委託給Adaptee    
    //其它行爲
  }
  
  //這裏run需要顯示實現
  public void run() {
    this.crossCountry.run();
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章