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