设计模式之二:Adapter(适配器模式)

来源:互联网 发布:鬼畜调教软件 编辑:程序博客网 时间:2024/05/21 08:45

    适配器模式是用来解决使用不兼容的接口的问题的方案。从下面可以看到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已经知道了客户期望的接口并已将其实现。

原创粉丝点击