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. * @version 1.0
  10. */
  11. class Target {
  12. /**
  13. * 这个方法将来有可能改进
  14. */
  15. public function hello(){
  16. echo ‘Hello ‘;
  17. }
  18. /**
  19. * 目标点
  20. */
  21. public function world(){
  22. echo ‘world‘;
  23. }
  24. }
  25. /**
  26. * Client 程序
  27. *
  28. */
  29. class Client {
  30. /**
  31. * Main program.
  32. */
  33. public static function main() {
  34. $Target = new Target();
  35. $Target->hello();
  36. $Target->world();
  37. }
  38. }
  39. Client::main();
  40. ?>

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

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

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

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

类适配器使用的是继承

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

对象适配器使用的是委派

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

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

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

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

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

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

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

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

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

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

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

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

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

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

时间: 2024-10-05 05:32:27

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

Adapter(适配器)模式

适配器模式保留现有类所提供的服务,向客户提供接口,以满足客户的期望.适配器模式可分为类适配器模式.对象适配器模式和标识适配器模式三种. 类适配器模式 拓展一个现有的类,并实现一个目标接口,将把客户的调用转变为调用现有类的方法. 对象适配器模式 拓展一个目标类,并把它委派给一个现有的类,将客户调用转发给现有类的实例. 如果希望适配的方法没有在接口中指定时,就应该使用委托方式,而不是创建子类方式.即对象适配器模式. 只要我们所适配的接口可以得到抽象类的支持,也必须使用对象适配器模式. 如果适配器类需

设计模式的征途—7.适配器(Adapter)模式

在现实生活中,我们的笔记本电脑的工作电压大多数都是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够工作在220V的电压下工作?答案:引入一个电源适配器,俗称变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容. 在软件开发中,有时候也会存在这种不兼容的情况,我们也可以像电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即称之为适配器模式. 适配器模式(Builder) 学习难度:★★☆☆☆ 使用频率:★★★★☆ 一.木有源码的算法库 Backgr

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

概述 什么是适配器?在我们生活中的适配器比如插头转换器(中标转美标).USB接口转换器(type-c转苹果),电脑电源适配器(交流电转低电压直流)等.像这种将两者有差异的东西通过适配器使他们成为相互适合的东西.在程序世界中,经常存在现有的程序无法直接使用,需要做适当的变换后才能使用的情况,这种用于填补“现有程序”和“所需程序”之间差异的设计模式就是适配器(Adapter)模式.适配器模式有类适配器模式和对象适配器模式两种,前者使用继承,后者使用组合,所以后者比较灵活,推荐使用.下面通过实例对这两

设计模式之适配器模式 adapter 适配器模式分类概念角色详解 类适配器 对象适配器 接口适配器 双向适配器

现实世界中的适配器模型 先来看下来几个图片,截图自淘宝 上图为港版的插头与港版的插座 上图为插座适配器卖家的描述图 上图为适配后的结果 现实世界中适配器模式 角色分类 这就是适配器模式在电源插座上的应用 我们看下在插座适配器中的几个重要角色 可以看得出来,大陆和港版插座面板,都是作为电源的角色,他们的功能是相似的或者说相近的 插头要使用插座,进而接通电流 现实世界到代码的转换 电源插座代码示例 港版插座面板 package adapter; /**目标角色 Target 接口 * 香港地区使用的

Android adapter适配器的使用

ListView之SimpleAdapter SimpleAdapter的构造函数是: public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) 参数context:上下文,比如this.关联SimpleAdapter运行的视图上下文 参数data:Map列表,列表要显示的数据,这部分需要自己实现,如例子中的ge

android学习笔记之ListView 和Adapter适配器

1.在学习Listview时候用到了Adapter适配器,定义MyAdapter时候需要继承ListAdapter接口,接口里很多方法没有实现,为了方便google工程师实现了个BaseAdapter类,我们在使用的时候可以继承这个抽象类,因此我们只需要完成几个抽象方法就可以了. public class Db_adapter extends BaseAdapter { private Context context; private List<Person> personlist; publ

七、适配器(Adapter)模式--结构模式(Structural Pattern)

适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作. 类的 Adapter模式的结构: 类适配器类图: 由图中可以看出,Adaptee 类没有 Request方法,而客户期待这个方法.为了使客户能够使用 Adaptee 类,提供一个中间环节,即类Adapter类, Adapter 类实现了 Target 接口,并继承 自 Adaptee,Adapter 类的 Request 方法重新封装了Adaptee 的SpecificRequ

Adapter(适配器)-类对象结构型模式

1.意图 将一个类接口转换成客户希望的另外一个接口.Adapter模式使那些原本不能一起工作的类,可以一起工作. 2.别名 包装器 Wrapper. 3.动机 一个应用可能会有一些类具有不同的接口,并且这些接口互不兼容,可以专门定义一个类,用来适配互不兼容的类. 4.适用性 你想使用一个已经存在的类,而它的接口不符合你的需求. 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作. 你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口.对象适配器可以适配

Java 适配器(Adapter)模式

一.什么是适配器模式: 把一个接口变成另外一个接口,使得原本因接口不匹配无法一起工作的两个类一起工作. 二.适配器模式的结构: (1)Target(目标抽象类):所期待的接口. (2)Adapter(适配器类):模式的核心类,作为转换器对Target和Adaptee进行适配. (3)Adaptee(适配者类):定义了需要适配的接口. (4)Client(客户类):针对目标抽象类编程,调用其定义的方法. 三.类适配器和对象适配器的比较: 类适配器中,适配器类通过实现Target接口并继承Adapt

JAVA设计模式(DESIGN PATTERNS IN JAVA)读书摘要 第1部分接口型模式——第3章 适配器(Adapter)模式

客户端代码提供接口来写具体实现类时,要利用已经实现接口功能的现有类,但是接口的方法名和现有类的方法名不一致,则需要使用适配器模式. 接口适配 如图所示, RequiredInterface接口声明了Client类所要调用的requiredMethod()方法,ExistingClass的usefulMethod()提供了此方法的具体实现,但是这两个方法的名字不同,这要对ExistingClass进行适配.适配类NewClass继承ExistingClass类,实现了RequiredInterfa