設計模式之二:Adapter(適配器模式)

    適配器模式是用來解決使用不兼容的接口的問題的方案。從下面可以看到2種適配器都有3個類分別是Target,   Adaptee,adapter.  
   
              client希望使用的是Target.request()  
   
              而既有類使用的是Adaptee.SpecificRequest()  
   
              request()和SpecificRequest()可能在返回值,參數列表都不同  
   
              類適配器模式和對象適配器模式都是通過嫁接一個Adapter進行轉換。  
   
              但是2種模式嫁接的方式不同。  
   
              類適配器模式採用的是多重繼承的方式  
   
              class   Adapter   extends   Adaptee   implements   Target{  
   
                          request(){this.SpecificRequest();}  
   
                          ....  
   
            }  
   
              對象適配器採用的是包含的方式  
   
              class   Adapter   implements   Taget{  
   
                        Adaptee   adaptee;  
   
                        request(){adaptee.SpecificRequest();}  
   
                        ...  
   
                }  
   
              兩者的區別我認爲有以下幾點  
   
              1.類適配器模式需要創建自身來創建一個Adaptee,  
   
                  對象適配器模式可以直接使用一個已有的Adaptee的實例來轉換接口。  
   
              2.   類適配器繼承了Adaptee,所以可以通過覆寫來擴展SpecificRequest()  
   
                  對象適配器和Adaptee是包含關係不能擴展;  

 

    適配器模式是用來解決使用不兼容的接口的問題的方案。從下面可以看到2種適配器都有3個類分別是Target,   Adaptee,adapter.  
   
              client希望使用的是Target.request()  
   
              而既有類使用的是Adaptee.SpecificRequest()  
   
              request()和SpecificRequest()可能在返回值,參數列表都不同  
   
              類適配器模式和對象適配器模式都是通過嫁接一個Adapter進行轉換。  
   
              但是2種模式嫁接的方式不同。  
   
              類適配器模式採用的是多重繼承的方式  
   
              class   Adapter   extends   Adaptee   implements   Target{  
   
                          request(){this.SpecificRequest();}  
   
                          ....  
   
            }  
   
              對象適配器採用的是包含的方式  
   
              class   Adapter   implements   Taget{  
   
                        Adaptee   adaptee;  
   
                        request(){adaptee.SpecificRequest();}  
   
                        ...  
   
                }  
   
              兩者的區別我認爲有以下幾點  
   
              1.類適配器模式需要創建自身來創建一個Adaptee,  
   
                  對象適配器模式可以直接使用一個已有的Adaptee的實例來轉換接口。  
   
              2.   類適配器繼承了Adaptee,所以可以通過覆寫來擴展SpecificRequest()  
   
                  對象適配器和Adaptee是包含關係不能擴展;  

一、適配器(Adapter)模式
  適配器模式把一個類的接口變換成客戶端所期待的另一種接口,從而使原本接口不匹配而無法在一起工作的兩個類能夠在一起工作。

  名稱由來
  這很像變壓器(Adapter),變壓器把一種電壓變換成另一種電壓。美國的生活用電電壓是110V,而中國的電壓是220V。如果要在中國使用美國電器,就必須有一個能把220V電壓轉換成110V電壓的變壓器。這個變壓器就是一個Adapter。

  Adapter模式也很像貨物的包裝過程:被包裝的貨物的真實樣子被包裝所掩蓋和改變,因此有人把這種模式叫做包裝(Wrapper)模式。事實上,大家經常寫很多這樣的Wrapper類,把已有的一些類包裝起來,使之有能滿足需要的接口。

  適配器模式的兩種形式

  適配器模式有類的適配器模式和對象的適配器模式兩種。我們將分別討論這兩種Adapter模式。


  二、類的Adapter模式的結構:


  由圖中可以看出,Adaptee類沒有Request方法,而客戶期待這個方法。爲了使客戶能夠使用Adaptee類,提供一箇中間環節,即類Adapter類,Adapter類實現了Target接口,並繼承自Adaptee,Adapter類的Request方法重新封裝了Adaptee的SpecificRequest方法,實現了適配的目的。

  因爲Adapter與Adaptee是繼承的關係,所以這決定了這個適配器模式是類的。

  該適配器模式所涉及的角色包括:

  目標(Target)角色:這是客戶所期待的接口。因爲C#不支持多繼承,所以Target必須是接口,不可以是類。
  源(Adaptee)角色:需要適配的類。
  適配器(Adapter)角色:把源接口轉換成目標接口。這一角色必須是類。


  三、類的Adapter模式示意性實現:

  下面的程序給出了一個類的Adapter模式的示意性的實現:
    //  Class Adapter pattern -- Structural example 
  using System;

    // "ITarget"
  interface ITarget
  {
     // Methods
   void Request();
   }

    // "Adaptee"
  class Adaptee
  {
     // Methods
   public void SpecificRequest()
    {
    Console.WriteLine("Called SpecificRequest()" );
    }
  }

    // "Adapter"
  class Adapter : Adaptee, ITarget
  {
     // Implements ITarget interface
    public void Request()
    {
      // Possibly do some data manipulation
      // and then call SpecificRequest
      this.SpecificRequest();
    }
  }

    /**//// <summary>
    /// Client test
    /// </summary>
  public class Client
  {
   public static void Main(string[] args)
   {
      // Create adapter and place a request
    ITarget t = new Adapter();
    t.Request();
    }
  }

四、對象的Adapter模式的結構

   

  從圖中可以看出:客戶端需要調用Request方法,而Adaptee沒有該方法,爲了使客戶端能夠使用Adaptee類,需要提供一個包裝(Wrapper)類Adapter。這個包裝類包裝了一個Adaptee的實例,從而將客戶端與Adaptee銜接起來。由於Adapter與Adaptee是委派關係,這決定了這個適配器模式是對象的。

  該適配器模式所涉及的角色包括:

  目標(Target)角色:這是客戶所期待的接口。目標可以是具體的或抽象的類,也可以是接口。
  源(Adaptee)角色:需要適配的類。
  適配器(Adapter)角色:通過在內部包裝(Wrap)一個Adaptee對象,把源接口轉換成目標接口。


  五、對象的Adapter模式示意性實現:
  下面的程序給出了一個類的Adapter模式的示意性的實現:

    // Adapter pattern -- Structural example 
  using System;

    // "Target"
  class Target
  {
     // Methods
   virtual public void Request()
    {
      // Normal implementation goes here
    }
  }

    // "Adapter"
  class Adapter : Target
  {
     // Fields
    private Adaptee adaptee = new Adaptee();

     // Methods
    override public void Request()
    {
      // Possibly do some data manipulation
      // and then call SpecificRequest
      adaptee.SpecificRequest();
    }
  }

    // "Adaptee"
  class Adaptee
  {
     // Methods
   public void SpecificRequest()
    {
      Console.WriteLine("Called SpecificRequest()" );
    }
  }

    /**//// <summary>
    /// Client test
    /// </summary>
  public class Client
  {
    public static void Main(string[] args)
    {
      // Create adapter and place a request
      Target t = new Adapter();
      t.Request();
    }
  } 

六、在什麼情況下使用適配器模式
  在以下各種情況下使用適配器模式:

  1、 系統需要使用現有的類,而此類的接口不符合系統的需要。
  2、 想要建立一個可以重複使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。這些源類不一定有很複雜的接口。
  3、 (對對象適配器而言)在設計裏,需要改變多個已有子類的接口,如果使用類的適配器模式,就要針對每一個子類做一個適配器,而這不太實際。


  七、一個實際應用Adapter模式的例子
  下面的程序演示了Class Adapter與Object Adapter的應用。

    // Example of implementing the Adapter pattern
  using System;

    // Target
  public interface  ICar
  {
    void  Drive();
  }

    // Direct use without Adapter
  public class  CToyota : ICar
  {
    public void  Drive()
    {
      Console.WriteLine("Vroom Vroom, we're off in our Toyota");
    }
  }

    // Adaptee
  public class  CCessna
  {
    public void  Fly()
    {
      Console.WriteLine("Static runup OK, we're off in our C172");
    }
  }

    // Class Adapter
  public class  CDrivableCessna : CCessna, ICar
  {
    public void  Drive()  {  base.Fly();  }
  }

    // Object Adapter
  public class  CDrivableCessna2 : ICar
  {
    private CCessna  m_oContained;

    public CDrivableCessna2()
    {
      m_oContained = new CCessna();
    }

    public void  Drive()  {  m_oContained.Fly();  }
  }

    // Client
  public class  Client
  {
    public static void  Main(string[] args)
    {
      ICar  oCar = new CToyota();

      Console.Write("Class Adapter: Driving an Automobile");
      oCar.Drive();
      oCar = new CDrivableCessna();
      Console.Write("Driving a Cessna");
      oCar.Drive();
      oCar = new CDrivableCessna2();
      Console.Write(" Object Adapter: Driving a Cessna");
      oCar.Drive();
    }
  }

  八、關於Adapter模式的討論
  Adapter模式在實現時有以下這些值得注意的地方:

  1、 目標接口可以省略,模式發生退化。但這種做法看似平庸而並不平庸,它可以使Adaptee不必實現不需要的方法(可以參考Default Adapter模式)。其表現形式就是父類實現缺省方法,而子類只需實現自己獨特的方法。這有些像模板(Template)模式。
  2、 適配器類可以是抽象類。
  3、 帶參數的適配器模式。使用這種辦法,適配器類可以根據參數返還一個合適的實例給客戶端。

 一、分類:


結構型模式

二、理解“適配”

“適配”其實就是一種轉換,這種轉換髮生在你不想改變某個東西現有功能,但又想把這個東西用在另外的一種新場合中。

絕大多數產品在設計之時是針對某個特定使用場合的,它向這個特定的使用場合公開一些接口以使客戶可以使用它。一旦其使用場合發生變化,其對外公開的接口可能就不再符合客戶的需求了。但與此同時,我們不想去改變原有的產品(如果你想改變這個原有產品的實現,那就不需要使用適配了),因爲它擁有很好的功能且已經在使用過程中經過了驗證,這個時候我們就需要“轉化”(適配)一下,提供一個適配器,將原來產品的接口轉化爲客戶期望的接口。這樣的例子在生活中很容易舉證,比如我們家庭中使用的很多電器所要求的電壓爲220,但也有一些電器要求更低的電壓,這個時候就出現了電源適配器,利用電源適配器來改變輸出的電壓以提供給低電壓的電器使用。

二、應用場景假設

其實上面的文件已經表示出了適配器模式使用的場合了。表現在軟件設計與開發中,就是由於客戶需求的變化,要將一些現有的對像放在一個新場合中使用,而新場合所期望的接口是這些原有對象所不能滿足的,而我們又不想(在某些情況下可能是你根本不能去改變的)改變這些原有實現。

三、意圖

將一個類的接口轉換爲客戶希望的另一個接口。Adapter使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。(Gof)

四、結構

Adapter模式有兩種結構,分別是類的適配與對象的適配。

1、類的適配

角色分析:Target目標角色:這是一個接口,定義了客戶所期望的操作Adaptee源角色:這是我們原有的產品,也是需要被適配的產品Adapter適配器角色:在Target目標角色與Adaptee源角色之間提供一種過渡,即把Adaptee源角色所提供的接口轉換爲Target目標角色所提供的接口。從結構圖中可以很容易的知道,適配器角色Adapter必須要繼承Targe目標角色(一個接口)與源角色Adaptee。2、對象的適配
兩種結構的角色其實都是一樣的,客戶的調用流程也是相同的。不同之處於兩者在包裝Adaptee源角色時,前者(類適配)包裝的是Adaptee類(因爲它同時從Target與Adaptee繼承而來,可想而知,類適配的Adatper必須是一個具體類,而Target只能是一個接口),後者(對象適配)則直接包裝了一個源Adaptee的實例。這一點如果放在代碼中,則更容易體現出來。此處的差別導致了在具體實現時各個角色的不同實現方式(以類還是以接口)。

五、代碼示例

以電壓的轉換爲例

1、類適配

/// <summary>
/// 目標接口,這是客戶要使用的,客戶需要30伏的電壓,這裏必須是一個接口,因爲C#不支持多重繼承
/// </summary>
public interface Target
{
         int Out();
}
/// <summary>
/// 標準電壓類,提供220伏電壓,這是現有對象(即源Adaptee)
/// </summary>
public class Tension
{
         public Tension()
         {
          //...
         }

         /// <summary>
         /// 輸出電壓
         /// </summary>
         /// <returns></returns>
         public int OutTension()
         {
          return 220 ;
         }
}

/// <summary>
/// 電源適配器類,同時繼承Target與Tension(源Adaptee對象),這裏必須是一個類
/// </summary>
public class PowerAdapter:Tension,Target
{
         public PowerAdapter()
         {
          //...
         }

         #region Target 成員
         public int Out()
         {
          //do something
          //...

          //call
          return base.OutTension() - 190 ;
         }
         #endregion
}

客戶調用:

          Target t = new PowerAdapter() ;

          Response.Write(t.Out().ToString()) ;

輸出結果:

30

2、對象適配

對象適配直接封裝了一個Adaptee實例,所以,Adapter適配器就可以不需要去繼承Adaptee源對象了,只需要繼承Target就可以了,這個時候由於Adapter只需要從單獨的Target繼承,所以,Target在實現時就不侷限於只是接口的規定了,它也可以是一個類。我們面邊還是把它寫成了接口,這沒關係。

/// <summary>
/// 目標接口,這是客戶要使用的,客戶需要30伏的電壓,這裏必須是一個接口,因爲C#不支持多重繼承
/// </summary>
public interface Target
{
         int Out();
}
/// <summary>
/// 標準電壓類,提供220伏電壓,這是現有對象(即源Adaptee)
/// </summary>
public class Tension
{
         public Tension()
         {
          //...
         }

         /// <summary>
         /// 輸出電壓
         /// </summary>
         /// <returns></returns>
         public int OutTension()
         {
          return 220 ;
         }
}

/// <summary>
/// 電源適配器類,同時繼承Target與Tension(源Adaptee對象),這裏必須是一個類
/// </summary>
public class PowerAdapter:Target
{
         private Tension MyTension = new Tension() ;

         public PowerAdapter()
         {
          //...
         }

         #region Target 成員
         public int Out()
         {
          //do something
          //...

          //call
          return MyTension.OutTension() - 190 ;
         }
         #endregion
}

客戶調用:

          Target t = new PowerAdapter() ;

          Response.Write(t.Out().ToString()) ;

程序輸出:

30

通過對代碼的分析不難發現,類適配使用的是多繼承來實現適配工作,本身類繼承可能會帶來的一個結果就是“高耦合”,所以推薦使用對象的適配而不是類的適配,因爲對象適配使用的是“對象組合”的方式,其帶來的“低耦合”使系統在未來更容易擴展。


六、關於適配器模式的演化


在我們觀察對象適配時,目標角色在某些情況下是完全可以被去掉了,客戶只需要直接使用適配器對象就OK了。如下代碼

/// <summary>
/// 標準電壓類,提供220伏電壓,這是現有對象(即源Adaptee)
/// </summary>
public class Tension
{
        public Tension()
        {
         //...
        }

        /// <summary>
        /// 輸出電壓
        /// </summary>
        /// <returns></returns>
        public int OutTension()
        {
         return 220 ;
        }
}

/// <summary>
/// 電源適配器類
/// </summary>
public class PowerAdapter
{
        private Tension MyTension = new Tension() ;

        public PowerAdapter()
        {
         //...
        }

        #region Target 成員
        public int Out()
        {
         //do something
         //...

         //call
         return MyTension.OutTension() - 190 ;
        }
        #endregion
}

客戶調用:

         PowerAdapter t = new PowerAdapter() ;

         Response.Write(t.Out().ToString()) ;

程序輸出:

30

這樣做有一個好處就是如果Target規定了很多與Adaptee使用無關的接口,那我們可以決定我們不需要實現Target的所有規定,當然前提是Adapter已經知道了客戶期望的接口並已將其實現。

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