在软件开发中,我们经常会遇到系统间集成,在系统集成时,最常见的问题就是系统间的接口不一致。很多能够满足功能的系统模块,由于接口不一致,导致无法使用。例如,常用的媒体播放器是MS Media player和RealPlayer,他们的文件结构和软件接口完全不同,前者支持WMF格式的音频和视频,后者支持RM格式的音频和视频。如果我们希望自己的软件能够播放这两种播放器播放相应格式的音频和视频,我们该怎么办呢?一切从头开始,重写一个支持这两种格式的播放软件?呵呵,你要不觉得累你就重写了,呵呵。
适配器模式(Adapter):将一个的接口转化成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的的那些类可以一起工作。 适配器模式的意图就是使接口不兼容的类能够一起工作,通常情况下,这些接口不兼容的类在逻辑上的功能是一致或相似的。在开发中,系统的数据和行为都正确,但接口不符时,我们就应该考虑用适配器模式,目的就在于使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要用于希望服用一些现有的类,但是接口又与环境要求不一致的情况。
适配器有类适配器和对象适配器两种类型,二者的意图相同,只是实现的方法和适用的情况不同。类适配器采用继承来实现,对象适配器则采用组合的方法来实现。
类适配器:类适配器通过多重继承对一个接口与另一个接口进行匹配,其结构如下图所示:
Target定义了Client使用的与特定领域相关的接口,Client通过调用Target实现某一特定的操作。Adaptee是一个已经存在的类,需要与Target协同工作,这个接口需要适配。Adapter适配器适配Adaptee和Target接口。在类适配器中,通过继承获得Adaptee中的方法:
/// <summary>
/// Adaptee
/// </summary>
public class Adaptee
{
public void Execute()
{
}
}
/// <summary>
/// Target
/// </summary>
public class Target
{
public void Do()
{
}
}
/// <summary>
/// Adapter,.NET中不支持多继承
/// </summary>
public class Adapter : Adaptee, Target
{
Target 成员
}
对象适配器:对象适配器采用对象组合,通过引用一个类与另一个类接口,其结构下图所示:
在对象适配器中,通过组合获得Adaptee对象:
/// <summary>
/// Adaptee
/// </summary>
public class Adaptee
{
public void Execute()
{
}
}
/// <summary>
/// Target
/// </summary>
public class Target
{
public void Do()
{
}
}
/// <summary>
/// Adapter,.NET中不支持多继承
/// </summary>
public class Adapter : Target
{
private Adaptee adaptee;
public Adapter(Adaptee ap)
{
adaptee = ap;
}
Target 成员
}
2.实例
大话设计模式中的篮球翻译适配器,先来看结构图:
代码如下:
//篮球运动员
public abstract class Player
{
protected string name;
public Player(string name)
{
this.name = name;
}
public abstract void Attack();
public abstract void Defense();
}
//前锋
public class Forwards : Player
{
public Forwards(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("前锋 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("前锋 {0} 防守", name);
}
}
//中锋
public class Center : Player
{
public Center(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("中锋 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("中锋 {0} 防守", name);
}
}
//后卫
public class Guards : Player
{
public Guards(string name)
: base(name)
{
}
public override void Attack()
{
Console.WriteLine("后卫 {0} 进攻", name);
}
public override void Defense()
{
Console.WriteLine("后卫 {0} 防守", name);
}
}
//外籍中锋
public class ForeignCenter
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public void 进攻()
{
Console.WriteLine("外籍中锋 {0} 进攻", name);
}
public void 防守()
{
Console.WriteLine("外籍中锋 {0} 防守", name);
}
}
//翻译者
public class Translator : Player
{
private ForeignCenter wjzf = new ForeignCenter();
public Translator(string name)
: base(name)
{
wjzf.Name = name;
}
public override void Attack()
{
wjzf.进攻();
}
public override void Defense()
{
wjzf.防守();
}
}
调用代码:
{
Player b = new Forwards("巴蒂尔");
b.Attack();
Player m = new Guards("麦克格雷迪");
m.Attack();
//Player ym = new Center("姚明");
Player ym = new Translator("姚明");
ym.Attack();
ym.Defense();
Console.Read();
}
前锋 巴蒂尔 进攻
后卫 麦克格雷迪 进攻
外籍中锋 姚明 进攻
外籍中锋 姚明 防守
有了翻译者(适配器),姚明不懂英文,火箭的教练和球员也不用学中文就可以使火箭整个团队沟通合作的很好。哈哈。
3.总结
1).NET中的适配器模式DataAdapter。DataAdapter用作DataSet和数据源之间的适配器以便检索和保存数据。DataAdapter通过映射Fill(更改了DataSet中的数据以便与数据源中的数据相匹配)和Update(更改了数据源中的数据以便与DataSet中的数据相匹配),来提供这一适配器。
实现要点
Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用;Adapter模式有对象适配器和类适配器两种形式的实现结构,但是类适配器采用“多继承”的实现方式,带来了不良的高耦合,所以一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合精神;Adapter模式的实现可以非常的灵活,不必拘泥于GOF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的;Adapter模式本身要求我们尽可能地使用“面向接口的编程”风格,这样才能在后期很方便的适配。
效果:
对于类适配器:用一个具体的Adapter类对Adaptee和Taget进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作;使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类;仅仅引入了一个对象,并不需要额外的指针一间接得到Adaptee.
对于对象适配器:允许一个Adapter与多个Adaptee,即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
适用性
在以下各种情况下使用适配器模式:系统需要使用现有的类,而此类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口;(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
在对两个无关类进行适配的时候考虑一下适配的代价,一个非常庞大的适配器可能会对系统性能有影响。