PHP设计模式(五)适配器模式Adapter实例详解【结构型】


Posted in PHP onMay 02, 2020

本文实例讲述了PHP设计模式:适配器模式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. 结构

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

PHP设计模式(五)适配器模式Adapter实例详解【结构型】

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

PHP设计模式(五)适配器模式Adapter实例详解【结构型】

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改变时,如何保护应用程序不受影响。

<?php
/**
 * 类适配器模式
 * @author guisu
 * 
 */
 
/**
 * 目标角色
 * @version 1.0
 */
class Target {
 
  /**
   * 这个方法将来有可能改进
   */
  public function hello(){
   echo 'Hello ';
  }
 
  /**
   * 目标点
   */
  public function world(){
   echo 'world';
  }
}
 
/**
 * Client 程序
 *
 */
class Client {
 
  /**
   * Main program.
   */
  public static function main() {
    $Target = new Target();
    $Target->hello();
    $Target->world();
 
  }
 
}
Client::main();
?>

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

<?php
/**
 * 类适配器模式
 * @author guisu
 * 
 */
 
/**
 * 目标角色
 * @version 2.0
 */
class Target {
 
  /**
   * 这个方法将来有可能继续改进
   */
  public function greet(){
   echo 'Greet ';
  }
 
  /**
   * 目标点
   */
  public function world(){
   echo 'world';
  }
}

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

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

类适配器使用的是继承:

<?php
/**
 * 类适配器模式
 * @author guisu
 * 
 */
 
/**
 * 目标角色
 * @version 2.0
 */
interface Target {
 
  /**
   * 源类的方法:这个方法将来有可能继续改进
   */
  public function hello();
 
  /**
   * 目标点
   */
  public function world();
}
 
/**
 * 源角色:被适配的角色
 */
class Adaptee {
 /**
   * 源类含有的方法
   */
  public function world() {
    echo ' world <br />';
  }
 
  /**
   * 加入新的方法
   */
  public function greet() {
    echo ' Greet ';
  }
}
 
/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {
 
  /**
   * 源类中没有world方法,在此补充
   */
  public function hello() {
    parent::greet();
  }
 
}
/**
 * 客户端程序
 *
 */
class Client {
 
  /**
   * Main program.
   */
  public static function main() {
    $adapter = new Adapter();
    $adapter->hello();
    $adapter->world();
  }
}
Client::main();
?>

对象适配器使用的是委派

<?php
/**
 * 类适配器模式
 * @author guisu
 * 
 */
 
/**
 * 目标角色
 * @version 2.0
 */
interface Target {
 
  /**
   * 源类的方法:这个方法将来有可能继续改进
   */
  public function hello();
 
  /**
   * 目标点
   */
  public function world();
}
 
/**
 * 源角色:被适配的角色
 */
class Adaptee {
 /**
   * 源类含有的方法
   */
  public function world() {
    echo ' world <br />';
  }
 
  /**
   * 加入新的方法
   */
  public function greet() {
    echo ' Greet ';
  }
}
 
/**
 * 类适配器角色
 */
class Adapter implements Target {
 
 private $_adaptee;
 /**
  * construct
  *
  * @param Adaptee $adaptee
  */
  public function __construct(Adaptee $adaptee) {
    $this->_adaptee = $adaptee;
  }
 
  /**
   * 源类中没有world方法,在此补充
   */
  public function hello() {
    $this->_adaptee->greet();
  }
 
  /**
   * 源类中没有world方法,在此补充
   */
  public function world() {
    $this->_adaptee->world();
  }
}
/**
 * 客户端程序
 *
 */
class Client {
 
  /**
   * Main program.
   */
  public static function main() {
   $adaptee = new Adaptee();
    $adapter = new Adapter($adaptee);
    $adapter->hello();
    $adapter->world();
  }
}
Client::main();
?>

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

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

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

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

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

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

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

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

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

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

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

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

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

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
php下保存远程图片到本地的办法
Aug 08 PHP
php cookie的操作实现代码(登录)
Dec 29 PHP
php IP转换整形(ip2long)的详解
Jun 06 PHP
PHP生成验证码时“图像因其本身有错无法显示”的解决方法
Aug 07 PHP
PHP 使用pcntl和libevent 实现Timer功能
Oct 27 PHP
ThinkPHP中的关联模型注意点
Jun 16 PHP
codeigniter发送邮件并打印调试信息的方法
Mar 21 PHP
PHP getallheaders无法获取自定义头(headers)的问题
Mar 23 PHP
PHP实现留言板功能的详细代码
Mar 25 PHP
PHP7.1实现的AES与RSA加密操作示例
Jun 15 PHP
PHP的mysqli_stmt_init()函数讲解
Jan 24 PHP
open_basedir restriction in effect. 原因与解决方法
Mar 14 PHP
PHP设计模式(四)原型模式Prototype实例详解【创建型】
May 02 #PHP
PHP设计模式(三)建造者模式Builder实例详解【创建型】
May 02 #PHP
PHP设计模式(一)工厂模式Factory实例详解【创建型】
May 02 #PHP
PHP设计模式概论【概念、分类、原则等】
May 01 #PHP
PHP设计模式之 策略模式Strategy详解【对象行为型】
May 01 #PHP
php如何获取Http请求
Apr 30 #PHP
PHP 命名空间和自动加载原理与用法实例分析
Apr 29 #PHP
You might like
德生1994机评
2021/03/02 无线电
解析php中curl_multi的应用
2013/07/17 PHP
PHP设计模式(五)适配器模式Adapter实例详解【结构型】
2020/05/02 PHP
用javascript getComputedStyle获取和设置style的原理
2008/10/10 Javascript
js下通过getList函数实现分页效果的代码
2010/09/17 Javascript
jquery easyui 结合jsp简单展现table数据示例
2014/04/18 Javascript
js实现不重复导入的方法
2016/03/02 Javascript
浅谈Sublime Text 3运行JavaScript控制台
2016/06/06 Javascript
微信小程序入门教程
2016/11/18 Javascript
jQuery基于排序功能实现上移、下移的方法
2016/11/26 Javascript
ES6通过babel转码使用webpack使用import关键字
2016/12/13 Javascript
原生JS实现在线问卷调查投票特效
2017/01/03 Javascript
简单的渐变轮播插件
2017/01/12 Javascript
js输入框使用正则表达式校验输入内容的实例
2017/02/12 Javascript
深入浅析Nodejs的Http模块
2017/06/20 NodeJs
JavaScript实现构造json数组的方法分析
2018/08/17 Javascript
详解使用jest对vue项目进行单元测试
2018/09/07 Javascript
IE11下处理Promise及Vue的单项数据流问题
2019/07/24 Javascript
详解Vue中Axios封装API接口的思路及方法
2020/10/10 Javascript
Python实现Windows上气泡提醒效果的方法
2015/06/03 Python
Python实现的RSS阅读器实例
2015/07/25 Python
一个基于flask的web应用诞生 记录用户账户登录状态(6)
2017/04/11 Python
多版本python的pip 升级后, pip2 pip3 与python版本失配解决方法
2019/09/11 Python
解决django后台管理界面添加中文内容乱码问题
2019/11/15 Python
Python连接Mysql进行增删改查的示例代码
2020/08/03 Python
CSS3解决移动页面上点击链接触发色块的问题
2016/06/03 HTML / CSS
台湾全方位线上课程与职能学习平台:TibaMe
2019/12/04 全球购物
STRATHBERRY苏贝瑞包包官网:西班牙高级工匠手工打造
2020/11/10 全球购物
Clos19英国:高档香槟、葡萄酒和烈酒在线购物平台
2020/07/10 全球购物
研究生毕业自我鉴定范文
2014/03/27 职场文书
婚假请假条格式及范文
2014/04/10 职场文书
岗位工作说明书
2014/07/29 职场文书
教师师德工作总结2015
2015/07/22 职场文书
go 原生http web 服务跨域restful api的写法介绍
2021/04/27 Golang
python3实现常见的排序算法(示例代码)
2021/07/04 Python
AngularJS实现多级下拉框
2022/03/25 Javascript