设计模式之适配器模式(Adapter)(6)
简介
在实际的开发过程中,由于应用环境的变化(例如使用语言的变化),我们需要的实现在新的环境中没有现存对象可以满足,但是其他环境却存在这样现存的对象。那么如果将“将现存的对象”在新的环境中进行调用呢?解决这个问题的办法就是我们本文要介绍的适配器模式——使得新环境中不需要去重复实现已经存在了的实现而很好地把现有对象(指原来环境中的现有对象)加入到新环境来使用。
定义
把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。适配器模式有类的适配器模式和对象的适配器模式两种形式,下面我们分别讨论这两种形式的实现和给出对应的类图来帮助大家理清类之间的关系。
类型
结构型模式
适配器模式的结构
类适配器模式和对象适配器模式两种不同的形式。
1. 类适配器模式
类图
图1 类适配器模式类图
2. 对象适配器模式
类图
图2 对象适配器模式类图
角色类
- 目标角色(Target):定义Client使用的与特定领域相关的接口。
- 客户角色(Client):与符合Target接口的对象协同。
- 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
- 适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.
使用场景
1) 系统需要使用现有的类,而这些类的接口不符合系统的接口。
2) 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3) 两个类所做的事情相同或相似,但是具有不同接口的时候。
4) 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
5) 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
优点
- 通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
- 复用了现存的类,解决了现存类和复用环境要求不一致的问题。
- 将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。
- 一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
对于对象适配器来说,更换适配器的实现过程比较复杂。
模拟场景
在生活中,我们买的电器插头是2个孔的,但是我们买的插座只有三个孔的,此时我们就希望电器的插头可以转换为三个孔的就好,这样我们就可以直接把它插在插座上,此时三个孔插头就是客户端期待的另一种接口,自然两个孔的插头就是现有的接口。
实现
C#
输出结果:
代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Adapter { class Program { static void Main(string[] args) { // 现在客户端可以通过适配器使用2个孔的插头了 IThreeHole threehole = new PowerAdapter(); threehole.Request(); // 现在客户端可以通过适配使用2个孔的插头了 ThreeHole_Object threehole_object = new PowerAdapter_Object(); threehole_object.Request(); Console.ReadLine(); } } #region 类适配器模式 /// <summary> /// 三个孔的插头,也就是适配器模式中的目标角色 /// </summary> public interface IThreeHole { void Request(); } /// <summary> /// 两个孔的插头,源角色——需要适配的类(Adaptee) /// </summary> public abstract class TwoHole { public void SpecificRequest() { Console.WriteLine("我是两孔插头(类适配器)"); } } /// <summary> /// 适配器类,接口要放在类的后面 /// 适配器类提供了三个孔插头的行为,但其本质是调用两个孔插头的方法 /// </summary> public class PowerAdapter : TwoHole, IThreeHole { /// <summary> /// 实现三个孔插头接口方法 /// </summary> public void Request() { // 调用两个孔插头方法 this.SpecificRequest(); } } #endregion #region 对象适配器 /// <summary> /// 三个孔的插头,也就是适配器模式中的目标(Target)角色 /// </summary> public class ThreeHole_Object { // 客户端需要的方法 public virtual void Request() { // 可以把一般实现放在这里 } } /// <summary> /// 两个孔的插头,源角色——需要适配的类 /// </summary> public class TwoHole_Object { public void SpecificRequest() { Console.WriteLine("我是两孔插头(对象适配器)"); } } /// <summary> /// 适配器类,这里适配器类没有TwoHole_Object类, /// 而是引用了TwoHole_Object对象,所以是对象的适配器模式的实现 /// </summary> public class PowerAdapter_Object : ThreeHole_Object { // 引用两个孔插头的实例,从而将客户端与TwoHole联系起来 public TwoHole_Object twoholeAdaptee = new TwoHole_Object(); /// <summary> /// 实现三个孔插头接口方法 /// </summary> public override void Request() { twoholeAdaptee.SpecificRequest(); } } #endregion }
Java
输出结果:
代码:
public class Program { public static void main(String[] args) { // TODO Auto-generated method stub // 现在客户端可以通过适配器使用2个孔的插头了 IThreeHole threehole = new PowerAdapter(); threehole.Request(); // 现在客户端可以通过适配使用2个孔的插头了 ThreeHole_Object threehole_object = new PowerAdapter_Object(); threehole_object.Request(); } } /** * 三个孔的插头,也就是适配器模式中的目标角色 */ interface IThreeHole { void Request(); } /** * 两个孔的插头,源角色——需要适配的类(Adaptee) */ class TwoHole { public void SpecificRequest() { System.out.println("我是两孔插头(类适配器)"); } } /** * 适配器类,接口要放在类的后面 * 适配器类提供了三个孔插头的行为,但其本质是调用两个孔插头的方法 */ class PowerAdapter extends TwoHole implements IThreeHole { // 实现三个孔插头接口方法 public void Request() { // 调用两个孔插头方法 this.SpecificRequest(); } } /** * 三个孔的插头,也就是适配器模式中的目标(Target)角色 */ class ThreeHole_Object { // 客户端需要的方法 public void Request() { // 可以把一般实现放在这里 } } /** * 两个孔的插头,源角色——需要适配的类 */ class TwoHole_Object { public void SpecificRequest() { System.out.println("我是两孔插头(对象适配器)"); } } /** * 适配器类,这里适配器类没有TwoHole类, * 而是引用了TwoHole对象,所以是对象的适配器模式的实现 */ class PowerAdapter_Object extends ThreeHole_Object { // 引用两个孔插头的实例,从而将客户端与TwoHole联系起来 public TwoHole_Object twoholeAdaptee = new TwoHole_Object(); // 实现三个孔插头接口方法 public void Request() { twoholeAdaptee.SpecificRequest(); } }
类适配器和对象适配器的权衡
1)类适配器模式:适配器继承自已实现的类(一般多重继承)。
Adapter与Adaptee是继承关系
- 用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集
- 仅仅引入一个对象,并不需要额外的指针以间接取得adaptee
2)对象适配器模式:适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。
Adapter与Adaptee是委托关系
- 允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能
- 使用重定义Adaptee的行为比较困难
应用举例
- 使用过ADO.NET的开发人员应该都用过DataAdapter,它就是用作DataSet和数据源之间的适配器。DataAdapter通过映射Fill和Update来提供这一适配器。
- 最典型的例子就是很多功能手机,每一种机型都自带充电器,有一天自带充电器坏了,万能充电器就是适配器。
适配器模式与其它相关模式
(1)桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口
(2)装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是decorator模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。
(3)外观模式(Facade模式):适配器模式的重点是改变一个单独类的API。Facade的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。
适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。
(4)代理模式(Proxy模式):在不改变它的接口的条件下,为另一个对象定义了一个代理。
(5)装饰者模式,适配器模式,外观模式三者之间的区别:
- 装饰者模式的话,它并不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。
- 适配器模式是将一个接口通过适配来间接转换为另一个接口。
- 外观模式的话,其主要是提供一个整洁的一致的接口给客户端。