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制作简单的内容采集器的原理分析
Oct 01 PHP
php 生成WML页面方法详解
Aug 09 PHP
php下使用curl模拟用户登陆的代码
Sep 10 PHP
php 读取文件头判断文件类型的实现代码
Aug 05 PHP
PHP 正则表达式小结
Feb 12 PHP
php 批量查询搜狗sogou代码分享
May 17 PHP
在WordPress中实现发送http请求的相关函数解析
Dec 29 PHP
PHP输出多个元素的排列或组合的方法
Mar 14 PHP
PHP在弹框中获取foreach中遍历的id值并传递给地址栏
Jun 13 PHP
laravel5.4生成验证码的实例讲解
Aug 05 PHP
phpStudy2016 配置多个域名期间遇到的问题小结
Oct 19 PHP
php微信公众号开发之图片回复
Oct 20 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
php数组的概述及分类与声明代码演示
2013/02/26 PHP
php笔记之:php数组相关函数的使用
2013/04/26 PHP
php使用parse_url和parse_str解析URL
2015/02/22 PHP
php时间戳转换代码详解
2019/08/04 PHP
js中判断控件是否存在
2010/08/25 Javascript
jQuery判断密码强度实现思路及代码
2013/04/24 Javascript
JS控制图片翻转示例代码(兼容firefox,ie,chrome)
2013/12/19 Javascript
JavaScript实现大数的运算
2014/11/24 Javascript
Angular.JS通过指令操作DOM的方法
2017/05/10 Javascript
基于layer.js实现收货地址弹框选择然后返回相应的地址信息
2017/05/26 Javascript
js Date()日期函数浏览器兼容问题解决方法
2017/09/12 Javascript
javascript实现电脑和手机版样式切换
2017/11/10 Javascript
angularJs利用$scope处理升降序的方法
2018/10/08 Javascript
element-ui组件中input等的change事件中传递自定义参数
2019/05/22 Javascript
解决Layui数据表格的宽高问题
2019/09/28 Javascript
JS面向对象之单选框实现
2020/01/17 Javascript
vue项目中微信登录的实现操作
2020/09/08 Javascript
Python实现二叉树结构与进行二叉树遍历的方法详解
2016/05/24 Python
python将ansible配置转为json格式实例代码
2017/05/15 Python
Python+OpenCV让电脑帮你玩微信跳一跳
2018/01/04 Python
Numpy 将二维图像矩阵转换为一维向量的方法
2018/06/05 Python
Python 实现域名解析为ip的方法
2019/02/14 Python
安装2019Pycharm最新版本的教程详解
2019/10/22 Python
python图的深度优先和广度优先算法实例分析
2019/10/26 Python
Pycharm创建项目时如何自动添加头部信息
2019/11/14 Python
Python模块/包/库安装的六种方法及区别
2020/02/24 Python
python matplotlib工具栏源码探析二之添加、删除内置工具项的案例
2021/02/25 Python
HTML5中视频音频的使用详解
2017/07/07 HTML / CSS
西班牙购买行李箱和背包网站:Maletas Greenwich
2019/10/08 全球购物
毕业自我评价范文
2013/11/17 职场文书
经典毕业生求职信
2014/07/12 职场文书
群众路线党员自我评议范文2014
2014/09/24 职场文书
教师自我剖析材料
2014/09/29 职场文书
业务内勤岗位职责
2015/04/13 职场文书
帝企鹅日记观后感
2015/06/10 职场文书
孙振耀退休感言
2015/08/01 职场文书