Adapter(适配器)模式

1. 概述:

         接口的改变,是一个需要程序员们必须(虽然很不情愿)接受和处理的普遍问题。程序提供者们修改他们的代码;系统库被修正;各种程序语言以及相关库的发展和进化。

        例子1:iphone4,你即可以使用UBS接口连接电脑来充电,假如只有iphone没有电脑,怎么办呢?苹果提供了iphone电源适配器。可以使用这个电源适配器充电。这个iphone的电源适配器就是类似我们说的适配器模式。(电源适配器就是把电源变成需要的电压,也就是适配器的作用是使得一个东西适合另外一个东西。)

       例子2:最典型的例子就是很多功能手机,每一种机型都自带有从电器,有一天自带充电器坏了,而且市场没有这类型充电器可买了。怎么办?万能充电器就可以解决。这个万能充电器就是适配器。

 

2. 问题

 

     你如何避免因外部库的API改变而带来的不便?假如你写了一个库,你能否提供一种方法允许你软件的现有用户进行完美地升级,即使你已经改变了你的API?为了更好地适宜于你的需要,你应该如何改变一个对象的接口?

 

3. 解决方案

 

        适配器(Adapter)模式为对象提供了一种完全不同的接口。你可以运用适配器(Adapter)来实现一个不同的类的常见接口,同时避免了因升级和拆解客户代码所引起的纠纷。

 

    适配器模式(Adapter Pattern),把一个类的接口变换成客户端所期待的另一种接口, Adapter模式使原本因接口不匹配(或者不兼容)而无法在一起工作的两个类能够在一起工作。又称为转换器模式、变压器模式、包装(Wrapper)器模式(把已有的一些类包装起来,使之能有满足需要的接口)。

 

 

     考虑一下当(不是假设!)一个第三方库的API改变将会发生什么。过去你只能是咬紧牙关修改所有的客户代码,而情况往往还不那么简单。你可能正从事一项新的项目,它要用到新版本的库所带来的特性,但你已经拥有许多旧的应用程序,并且它们与以前旧版本的库交互运行地很好。你将无法证明这些新特性的利用价值,如果这次升级意味着将要涉及到其它应用程序的客户代码。

4. 分类

共有两类适配器模式:1.类的适配器模式(采用继承实现)2.对象适配器(采用对象组合方式实现)

1)类适配器模式    ——适配器继承自已实现的类(一般多重继承)。

 

Adapter与Adaptee是继承关系
1、用一个具体的Adapter类和Target进行匹配。结果是当我们想要一个匹配一个类以及所有它的子类时,类Adapter将不能胜任工作
2、使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子集
3、仅仅引入一个对象,并不需要额外的指针以间接取得adaptee

2)对象适配器模式—— 适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。

 

Adapter与Adaptee是委托关系
1、允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能
2、使用重定义Adaptee的行为比较困难
无论哪种适配器,它的宗旨都是:保留现有类所提供的服务,向客户提供接口,以满足客户的期望。
即在不改变原有系统的基础上,提供新的接口服务。

5. 适用性

 

以下情况使用Adapter模式
1 • 你想使用一个已经存在的类,而它的接口不符合你的需求。
2 • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
3 •(仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。即仅仅引入一个对象,并不需要额外的指针以间接取得adaptee。

 

6. 结构

类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示:

对象匹配器依赖于对象组合,如下图所示:

7. 构建模式的组成

目标角色(Target):— 定义Client使用的与特定领域相关的接口。
 客户角色(Client):与符合Target接口的对象协同。
• 被适配角色(Adaptee):定义一个已经存在并已经使用的接口,这个接口需要适配。
 适配器角色(Adapte) :适配器模式的核心。它将对被适配Adaptee角色已有的接口转换为目标角色Target匹配的接口。对Adaptee的接口与Target接口进行适配.

8. 效果

类适配器和对象适配器有不同的权衡。
类适配器
• 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
• 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
• 仅仅引入了一个对象,并不需要额外的指针以间接得到 Adaptee。

 

对象适配器则
• 允许一个Adapter与多个Adaptee—即Adaptee本身以及它的所有子类(如果有子类的话)—同时工作。Adapter也可以一次给所有的Adaptee添加功能。
• 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

使用Adapter模式时需要考虑的其他一些因素有

1) Adapter的匹配程度 对Adaptee的接口与Target的接口进行匹配的工作量各个Adapter可能不一样。工作范围可能是,从简单的接口转换(例如改变操作名 )到支持完全不同的操作集合。Adapter的工作量取决于Target接口与Adaptee接口的相似程度
2) 可插入的Adapter   当其他的类使用一个类时,如果所需的假定条件越少,这个类就更具可复用性。如果将接口匹配构建为一个类,
就不需要假定对其他的类可见的是一个相同的接口。也就是说,接口匹配使得我们可以将自己的类加入到一些现有的系统中去,
而这些系统对这个类的接口可能会有所不同。 
3) 使用双向适配器提供透明操作 使用适配器的一个潜在问题是,它们不对所有的客户都透明。被适配的对象不再兼容 Adaptee的接口,
因此并不是所有 Adaptee对象可以被使用的地方它都可以被使用。双向适配器提供了这样的透明性。
在两个不同的客户需要用不同的方式查看同一个对象时,双向适配器尤其有用。

9. 实现

类适配器使用的是继承

让我们看看当API改变时,如何保护应用程序不受影响。

 

  1. <?php  
  2. /** 
  3.  * 类适配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目标角色 
  10.  * @version 1.0 
  11.  */  
  12. class Target {  
  13.    
  14.     /** 
  15.      * 这个方法将来有可能改进 
  16.      */  
  17.     public function hello(){  
  18.         echo 'Hello ';  
  19.     }  
  20.    
  21.     /** 
  22.      * 目标点 
  23.      */  
  24.     public function world(){  
  25.         echo 'world';  
  26.     }  
  27. }  
  28.    
  29. /** 
  30.  * Client 程序 
  31.  * 
  32.  */  
  33. class Client {  
  34.   
  35.     /** 
  36.      * Main program. 
  37.      */  
  38.     public static function main() {  
  39.         $Target = new Target();  
  40.         $Target->hello();  
  41.         $Target->world();  
  42.    
  43.     }  
  44.    
  45. }  
  46. Client::main();  
  47. ?>  

 

 

我们Target已经明确指出hello()方法会在未来的版本中改进,甚至不被支持或者淘汰。接下来,现在假设第二版的Target已经发布。一个全新的greet()方法代替了hello()。

 

  1. <?php  
  2. /** 
  3.  * 类适配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目标角色 
  10.  * @version 2.0 
  11.  */  
  12. class Target {  
  13.    
  14.     /** 
  15.      * 这个方法将来有可能继续改进 
  16.      */  
  17.     public function greet(){  
  18.         echo 'Greet ';  
  19.     }  
  20.    
  21.     /** 
  22.      * 目标点 
  23.      */  
  24.     public function world(){  
  25.         echo 'world';  
  26.     }  
  27. }  

如果我们继续使用原来的client代码,肯定会报错,找不到hello方法。

 

 

针对API“升级”的解决办法就是创建一个适配器(Adapter)。

 

类适配器使用的是继承

  1. <?php  
  2. /** 
  3.  * 类适配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目标角色 
  10.  * @version 2.0 
  11.  */  
  12. interface Target {  
  13.    
  14.     /** 
  15.      * 源类的方法:这个方法将来有可能继续改进 
  16.      */  
  17.     public function hello();  
  18.    
  19.     /** 
  20.      * 目标点 
  21.      */  
  22.     public function world();  
  23. }  
  24.    
  25. /** 
  26.  * 源角色:被适配的角色 
  27.  */  
  28. class Adaptee {  
  29.     /** 
  30.      * 源类含有的方法 
  31.      */  
  32.     public function world() {  
  33.         echo ' world <br />';  
  34.     }  
  35.    
  36.     /** 
  37.      * 加入新的方法 
  38.      */  
  39.     public function greet() {  
  40.         echo ' Greet ';  
  41.     }  
  42. }  
  43.    
  44. /** 
  45.  * 类适配器角色 
  46.  */  
  47. class Adapter extends Adaptee implements Target {  
  48.    
  49.     /** 
  50.      * 源类中没有world方法,在此补充 
  51.      */  
  52.     public function hello() {  
  53.        parent::greet();  
  54.     }  
  55.    
  56. }  
  57. /** 
  58.  * 客户端程序 
  59.  * 
  60.  */  
  61. class Client {  
  62.    
  63.     /** 
  64.      * Main program. 
  65.      */  
  66.     public static function main() {  
  67.         $adapter = new Adapter();  
  68.         $adapter->hello();  
  69.         $adapter->world();  
  70.     }  
  71. }  
  72. Client::main();  
  73. ?>  

对象适配器使用的是委派

  1. <?php  
  2. /** 
  3.  * 类适配器模式 
  4.  * @author guisu 
  5.  *  
  6.  */  
  7.    
  8. /** 
  9.  * 目标角色 
  10.  * @version 2.0 
  11.  */  
  12. interface Target {  
  13.    
  14.     /** 
  15.      * 源类的方法:这个方法将来有可能继续改进 
  16.      */  
  17.     public function hello();  
  18.    
  19.     /** 
  20.      * 目标点 
  21.      */  
  22.     public function world();  
  23. }  
  24.    
  25. /** 
  26.  * 源角色:被适配的角色 
  27.  */  
  28. class Adaptee {  
  29.     /** 
  30.      * 源类含有的方法 
  31.      */  
  32.     public function world() {  
  33.         echo ' world <br />';  
  34.     }  
  35.    
  36.     /** 
  37.      * 加入新的方法 
  38.      */  
  39.     public function greet() {  
  40.         echo ' Greet ';  
  41.     }  
  42. }  
  43.    
  44. /** 
  45.  * 类适配器角色 
  46.  */  
  47. class Adapter  implements Target {  
  48.   
  49.     private $_adaptee;  
  50.     /** 
  51.      * construct 
  52.      * 
  53.      * @param Adaptee $adaptee 
  54.      */  
  55.     public function __construct(Adaptee $adaptee) {  
  56.         $this->_adaptee = $adaptee;  
  57.     }  
  58.    
  59.     /** 
  60.      * 源类中没有world方法,在此补充 
  61.      */  
  62.     public function hello() {  
  63.        $this->_adaptee->greet();  
  64.     }  
  65.    
  66.     /** 
  67.      * 源类中没有world方法,在此补充 
  68.      */  
  69.     public function world() {  
  70.        $this->_adaptee->world();  
  71.     }  
  72. }  
  73. /** 
  74.  * 客户端程序 
  75.  * 
  76.  */  
  77. class Client {  
  78.    
  79.     /** 
  80.      * Main program. 
  81.      */  
  82.     public static function main() {  
  83.         $adaptee = new Adaptee();  
  84.         $adapter = new Adapter($adaptee);  
  85.         $adapter->hello();  
  86.         $adapter->world();  
  87.     }  
  88. }  
  89.   
  90. Client::main();  
  91. ?>  

 

如例中代码所示,你可以运用适配器(Adapter)模式来避免因外部库改变所带来的不便——倘若向上兼容。作为某个库的开发者,你应该独立编写适配器,使你的用户更简便地使用新版本的库,而不用去修改他们现有的全部代码。

     GoF书中提出的适配器(Adapter)模式更倾向于运用继承而不是组成。这在强类型语言中是有利的,因为适配器(Adapter)事实上是一个目标类的子类,因而能更好地与类中方法相结合。

了更好的灵活性,我个人比较倾向于组成的方法(特别是在结合了依赖性倒置的情况下);尽管如此,继承的方法提供两种版本的接口,或许在你的实际运用中反而是一个提高灵活性的关键。

10.适配器模式与其它相关模式

桥梁模式(bridge模式)桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口

装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是decorator模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。

Facade(外观模式)适配器模式的重点是改变一个单独类的API。Facade的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。

适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口

 

代理模式(Proxy )在不改变它的接口的条件下,为另一个对象定义了一个代理。

 

装饰者模式,适配器模式,外观模式三者之间的区别:

装饰者模式的话,它并不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。

适配器模式是将一个接口通过适配来间接转换为另一个接口。

外观模式的话,其主要是提供一个整洁的一致的接口给客户端。

 

时间: 2024-10-25 18:57:39

Adapter(适配器)模式的相关文章

Android控件系列之相册Gallery&amp;Adapter适配器入门&amp;控件缩放动画入门

学习目的: 1.掌握在Android中如何建立Gallery 2.初步理解Android适配器的原理 3.实现简单的控件缩放动画 简介: 1.Gallery是Android内置的一个控件,它可以继承若干图片甚至是其他控件 2.Gallery自带了滚动播放图片功能,此功能您可以通过模拟器拖曳鼠标或者在手机上拖拽验证 3.Gallery需要适配器来传输数据,如果您不熟悉"适配器设计模式",可以将适配器理解为某厂商的电脑适配器,只要这个厂商的所有型号的电脑都能使用该适配器,也就是说,设计新型

C#设计模式之适配器设计模式(Adapter)

结构模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构.结构模式描述两种不同的东西:类与类的实例.根据这一点,结构模式可以分为类的结构模式和对象的结构模式. 后续内容将包括以下结构模式: 适配器模式(Adapter):Match interfaces of different classes 合成模式(Composite):A tree structure of simple and composite objects 装饰模式(Decorator):Add

人人都会设计模式:04-适配器模式--Adapter

版权声明:本文为博主原创文章,未经博主允许不得转载 公众号:TigerChain 添加公号更多文章等着你 作者: TigerChain 教程简介 1.阅读对象 本篇教程适合新手阅读,老手直接略过 2.教程难度 初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢 正文 一.什么是适配器模式 1.生活中的适配器 比如电脑转接器「这里主要是指连接电脑和投影仪的」,以我的 MAC 电脑为例子,我们公司的投影支持 VGA 和 HDMI ,但是我的 MAC 电脑只有一个 MINI DP 接

Java Adapter 适配器模式(类适配器,对象适配器)优缺点对比_java

Java 适配器模式            最近学习java 基础知识,学习适配器的时候疑惑很多,上网查下资料,对于 Adapter的资料很多,但是比较下本篇不错,这里记录下,大家需要的可以看下. 适配器模式就是把一个类的接口转换成客户端所期待的另外一种接口,从而使原接口不匹配而无法在一起工作的的两个类能在一起工作.从功能上说,这些接口不兼容的类一般具有相同或相似的功能.通常我们通过修改该类的接口来解决这种接口不兼容的情形,但是如果我们不愿意为了一个应用而修改各原接口,或者我们压根儿就没有对象的

VirtualBox linux 网络设置 Bridged Adapter模式

virtualbox下linux 请检查有没有设置开机启动网络,默认是没有开启的. #vi /etc/sysconfig/network-scripts/ifcfg-eth0 ONBOOT=yes 这里ONBOOT设置成YES,其他如IP.网关.DNS都无需设置开启DHCP都会自动获取! 固定ip设置 vi /etc/sysconfig/network-scripts/ifcfg-enp0s3 : TYPE=Ethernet BOOTPROTO=static #dhcp 换成static DEF

C#中的Adapter设计模式浅析

把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作. 意图 把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作. 场景 假设网络游戏的客户端程序分两部分.一部分是和服务端通讯的大厅部分,大厅部分提供的功能有道具购买.读取房间列表.创建房间以及启动游戏程 序.另一部分就是游戏程序了,游戏程序和大厅程序虽然属于一个客户端,但是由不同的公司在进行开发.游戏大厅通过实现约定的接口和游戏程序进行通讯

解读设计模式----适配器模式(Adapter Pattern)

在金庸笔下,三大神功都是难得之宝,多少人为得到他而......,仔细的分析下这三大神功,还是北冥较好,呵呵.我们从软件设计的角度来看,这不知算不算得上是一种复用(功力复用)的思想,只不过有点残忍罢.而在软件设计领域里,"复用"在某些时候也会出现很多问题,比如平台不兼容,开发语言不同或是接口不同等多种原因,弄得不好会不会出现既浪费了别人的现有资源,而自己的系统又无法完成呢?这有点像吸星----损人又损己. 企图将设计做好,就能够一劳永逸地坐享其成,这样的愿望就好上面所提到的吸星神功一般,

Adapter 适配器模式(设计模式03)

疑问: 在软件系统中,由于应用环境的变化,常常需要将"一些现存的对象"放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的. 如何应对这种"迁移的变化"? 如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口? 定义: 将一个类的接口转换成客户希望的另一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. --<设计模式>GoF 一.对象适配器 对象适配器采用对象组合,通过引用一个类与另一个类接

设计模式之Facade(外观)模式

我们通过一个简单的开关电脑模拟程序来说明Facade模式(外观模式) CPU类 public class Cpu {  public void start()  {   system.out.print("CPU启动");  }  public void stop()  {   system.out.print("CPU关闭");  } }   电脑风扇类 public class Fan {  public void start()  {   system.out

JavaScript设计模式之工厂模式和构造器模式_javascript技巧

什么是模式 前阵子准备期末考试,劳神又伤身的,实在闲不得空来更新文章,今天和大家说说javascript中的设计模式. 首先呢,我们需要知道的是:模式是一种可复用的解决方案,而反模式呢就是针对某个问题的不良解决方案. js反模式常见例子 1.向setTimeout和setInterval传递字符串,而不是函数,这会触发eval()的内部使用. 2.在全局上下文中定义大量的变量污染全局命名空间 3.修改Object类的原型 4.以内联形式使用js,嵌入在HTML文件中的js代码是无法包含在外部单元