PHP设计模式之适配器模式(Adapter)原理与用法详解


Posted in PHP onDecember 12, 2019

本文实例讲述了PHP设计模式之适配器模式(Adapter)原理与用法。分享给大家供大家参考,具体如下:

这个适配器模式,就是为了将一个类的接口转换成客户希望的另外一个接口,并且使用原本不兼容的而不能在一起工作的那些类可以在一起工作。它的核心思想就是把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

我们来考虑下开发过程中,我们引用一个第三方类库的场景,这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码,这个时候,为了减少工作量,我们就可以使用适配器模式。

先来看一个网上的案例:

  • 假如我们原始的有一个UserInfo的类,提供用户信息的类,早起设计该类的时候,只实现了一个getUserName获取用户名的方法。
  • 我们的MyOldObject类中,将从UserInfo这个类中获取用户信息,并且输出用户名
  • 随着时间的推移,我们旧的UserInfo这个类只提供的获取用户名的方法,已经没法满足需求,我们同时需要获取用户的年龄等信息。
  • 为了不改变原本UserInfo这个类,我们就继承UserInfo,建立一个UserInfoAdapter类,实现getAge获取年龄这样的方法。
  • 在我们的MyNewObject新的类中,我们实例化UserInfoAdapter,打印出用户姓名和年龄。
  • 这样,随着我们的扩展,我们没有改变原先UserInfo这个类和使用这个类的接口,我们通过适配的方法,将UserInfo类扩展出来

代码实现过程如下:

<?php
//早期的一个用户类,只实现获取用户名的方法
class UserInfo {
    public function getUserName() {
        return 'initphp';
    }
}
//MyOldObject类,从UserInfo类中获取信息,输出用户名
<?php
include_once("UserInfo.php");
class MyOldObject {
    public function write() {
        $UserInfo = new UserInfo;
        echo $UserInfo->getUserName();
    }
}
$a = new MyOldObject;
$a->write();

上述代码是早期的时候,我们使用的案例。然而UserInfoAdapter类,随着时间推移,项目需求在变化,UserInfo类无法满足需求,我们做了UserInfo类的适配器,满足新功能的需求,如下:

<?php
include_once("UserInfo.php");
class UserInfoAdapter extends UserInfo{
    public function getUserAge() {
        return 28;
    }
    public function getUser() {
        return array(
            'username' => $this->getUserName(),
            'age' => $this->getUserAge()
        );
    }
}

MyNewObject类,新功能的类,需要打印出用户年龄和姓名,UserInfo类无法满足需求,需要调用UserInfoAdapter适配器这个类,如下:

<?php
include_once("UserInfoAdapter.php");
class MyNewObject {
    public function write() {
        $UserInfoAdapter = new UserInfoAdapter;
        print_r($UserInfoAdapter->getUser());
    }
}
$a = new MyNewObject;
$a->write();

大概了解了哈,接下来咱们通过一个故事来了解下。

开始的时候,黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物,并且每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。在这个时候,黑枣玩具公司的程序猿就定义一个抽象类Toy,甚至是接口Toy,完事其他的类去继承父类,实现父类的方法,很和谐的是吧。

后来,为了扩大业务,也因为红枣遥控公司可以使用遥控设备对动物进行嘴巴控制,黑枣玩具公司打算与红枣遥控公司合作。不过,麻烦的是,红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。所以,黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

在考虑实现的方法时,黑枣玩具公司的程序猿可以再在他们的父类子类里给红枣遥控公司添加这么两个方法就好啦。但是,当黑枣玩具公司的程序猿一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”,这样做下去程序员会觉得自己很傻。

咱也不废话了,先来看下最开始的时候的代码:

abstract class Toy
{
  public abstract function openMouth();
  public abstract function closeMouth();
}
class Dog extends Toy
{
  public function openMouth()
  {
    echo "Dog open Mouth\n";
  }
  public function closeMouth()
  {
    echo "Dog open Mouth\n";
  }
}
class Cat extends Toy
{
  public function openMouth()
  {
    echo "Cat open Mouth\n";
  }
  public function closeMouth()
  {
    echo "Cat open Mouth\n";
  }
}

完事,因为绿枣遥控公司遥控设备更便宜稳定,所以黑枣玩具公司又打算要与绿枣遥控公司合作。

不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。

在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃的。

那么,问题出在哪里呢?

其实就是一开始的代码设计实现违反了“开-闭”原则,也就是一个软件实体应当对扩展开放,对修改关闭。也就是说,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

来看下最后的结果:

<?php
abstract class Toy
{
  public abstract function openMouth();
  public abstract function closeMouth();
}
class Dog extends Toy
{
  public function openMouth()
  {
    echo "Dog open Mouth\n";
  }
  public function closeMouth()
  {
    echo "Dog close Mouth\n";
  }
}
class Cat extends Toy
{
  public function openMouth()
  {
    echo "Cat open Mouth\n";
  }
  public function closeMouth()
  {
    echo "Cat close Mouth\n";
  }
}
//目标角色:红枣遥控公司
interface RedTarget
{
  public function doMouthOpen();
  public function doMouthClose();
}
//目标角色:绿枣遥控公司及
interface GreenTarget
{
  public function operateMouth($type = 0);
}
//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
  private $adaptee;
  function __construct(Toy $adaptee)
  {
    $this->adaptee = $adaptee;
  }
  //委派调用Adaptee的sampleMethod1方法
  public function doMouthOpen()
  {
    $this->adaptee->openMouth();
  }
  public function doMouthClose()
  {
    $this->adaptee->closeMouth();
  }
}
//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
  private $adaptee;
  function __construct(Toy $adaptee)
  {
    $this->adaptee = $adaptee;
  }
  //委派调用Adaptee:GreenTarget的operateMouth方法
  public function operateMouth($type = 0)
  {
    if ($type) {
      $this->adaptee->openMouth();
    } else {
      $this->adaptee->closeMouth();
    }
  }
}
class testDriver
{
  public function run()
  {
     //实例化一只狗玩具
    $adaptee_dog = new Dog();
    echo "给狗套上红枣适配器\n";
    $adapter_red = new RedAdapter($adaptee_dog);
    //张嘴
    $adapter_red->doMouthOpen();
    //闭嘴
    $adapter_red->doMouthClose();
    echo "给狗套上绿枣适配器\n";
    $adapter_green = new GreenAdapter($adaptee_dog);
    //张嘴
    $adapter_green->operateMouth(1);
    //闭嘴
    $adapter_green->operateMouth(0);
  }
}
$test = new testDriver();
$test->run();

大概了解了使用方式之后,我们来看下适配器模式之中的主要角色:

  1. 目标(Target)角色:定义客户端使用的与特定领域相关的接口,这也就是我们所期待得到的
  2. 源(Adaptee)角色:需要进行适配的接口
  3. 适配器(Adapter)角色:对Adaptee的接口与Target接口进行适配;适配器是本模式的核心,适配器把源接口转换成目标接口,此角色为具体类

使用场景如下:

   1、你想使用一个已经存在的类,而它的接口不符合你的需求
   2、你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作
   3、你想使用一个已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口(仅限于对象适配器)

再来看下类适配器和对象适配器的一些解释和区别:

类适配器:Adapter与Adaptee是继承关系

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

对象适配器:Adapter与Adaptee是委托关系

   1、允许一个Adapter与多个Adaptee同时工作。Adapter也可以一次给所有的Adaptee添加功能
   2、使用重定义Adaptee的行为比较困难

再来看下其它和适配器模式的对比:

  1. 桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同,桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口
  2. 装饰器模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。

最后来看下类适配器和对象适配器案例,如下:

//类适配器使用的是继承
<?php
/**
 * 目标角色
 */
interface Target {
 /**
  * 源类也有的方法1
  */
 public function sampleMethod1();
 /**
  * 源类没有的方法2
  */
 public function sampleMethod2();
}
/**
 * 源角色
 */
class Adaptee {
 /**
  * 源类含有的方法
  */
 public function sampleMethod1() {
  echo 'Adaptee sampleMethod1 <br />';
 }
}
/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {
 /**
  * 源类中没有sampleMethod2方法,在此补充
  */
 public function sampleMethod2() {
  echo 'Adapter sampleMethod2 <br />';
 }
}
class Client {
 /**
  * Main program.
  */
 public static function main() {
  $adapter = new Adapter();
  $adapter->sampleMethod1();
  $adapter->sampleMethod2();
 }
}
Client::main();
?>
//对象适配器使用的是委派
<?php
/**
 * 目标角色
 */
interface Target {
 /**
  * 源类也有的方法1
  */
 public function sampleMethod1();
 /**
  * 源类没有的方法2
  */
 public function sampleMethod2();
}
/**
 * 源角色
 */
class Adaptee {
 /**
  * 源类含有的方法
  */
 public function sampleMethod1() {
  echo 'Adaptee sampleMethod1 <br />';
 }
}
/**
 * 类适配器角色
 */
class Adapter implements Target {
 private $_adaptee;
 public function __construct(Adaptee $adaptee) {
  $this->_adaptee = $adaptee;
 }
 /**
  * 委派调用Adaptee的sampleMethod1方法
  */
 public function sampleMethod1() {
  $this->_adaptee->sampleMethod1();
 }
 /**
  * 源类中没有sampleMethod2方法,在此补充
  */
 public function sampleMethod2() {
  echo 'Adapter sampleMethod2 <br />';
 }
}
class Client {
 /**
  * Main program.
  */
 public static function main() {
  $adaptee = new Adaptee();
  $adapter = new Adapter($adaptee);
  $adapter->sampleMethod1();
  $adapter->sampleMethod2();
 }
}
Client::main();
?>

好啦,本次记录就到这里了。

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

PHP 相关文章推荐
Windows IIS PHP 5.2 安装与配置方法
Jun 08 PHP
php中通过数组进行高效随机抽取指定条记录的算法
Sep 09 PHP
php分页函数示例代码分享
Feb 24 PHP
php跨站攻击实例分析
Oct 28 PHP
php提示Warning:mysql_fetch_array() expects的解决方法
Dec 16 PHP
php根据日期或时间戳获取星座信息和生肖等信息
Oct 20 PHP
php验证身份证号码正确性的函数
Jul 20 PHP
yii2 RBAC使用DbManager实现后台权限判断的方法
Jul 23 PHP
详解Yii2.0 rules验证规则集合
Mar 21 PHP
php实现与python进行socket通信的方法示例
Aug 30 PHP
Laravel框架实现多数据库连接操作详解
Jul 12 PHP
php设计模式之迭代器模式实例分析【星际争霸游戏案例】
Apr 07 PHP
PHP学习记录之常用的魔术常量详解
Dec 12 #PHP
记Laravel调用Gin接口调用formData上传文件的实现方法
Dec 12 #PHP
PHP命名空间(namespace)原理与用法详解
Dec 11 #PHP
在 Laravel 6 中缓存数据库查询结果的方法
Dec 11 #PHP
PHP超级全局变量【$GLOBALS,$_SERVER,$_REQUEST等】用法实例分析
Dec 11 #PHP
关于PHP5.6+版本“No input file specified”问题的解决
Dec 11 #PHP
Laravel5.1 框架数据库操作DB运行原生SQL的方法分析
Jan 07 #PHP
You might like
php 抽象类的简单应用
2011/09/06 PHP
ThinkPHP开发框架函数详解:C方法
2015/08/14 PHP
thinkPHP商城公告功能开发问题分析
2016/12/01 PHP
Laravel框架生命周期与原理分析
2018/06/12 PHP
浅谈Laravel POST,PUT,PATCH 路由的区别
2019/10/15 PHP
JavaScript在IE中“意外地调用了方法或属性访问”
2008/11/19 Javascript
对 lightbox JS 图片控件进行了一下改造, 使其他支持复杂的图片说明
2010/03/20 Javascript
jquery photoFrame 图片边框美化显示插件
2010/06/28 Javascript
div拖拽插件——JQ.MoveBox.js(自制JQ插件)
2013/05/17 Javascript
jquery获取一组checkbox的值(实例代码)
2013/11/04 Javascript
JS获取当前网址、主机地址项目根路径
2013/11/19 Javascript
jquery获得option的值和对option进行操作
2013/12/13 Javascript
table对象中的insertRow与deleteRow使用示例
2014/01/26 Javascript
js实现下拉框选择要显示图片的方法
2015/02/16 Javascript
Javascript获取表单名称(name)的方法
2015/04/02 Javascript
Bootstrap基本插件学习笔记之模态对话框(16)
2016/12/08 Javascript
Bootstrap3.3.7导航栏下拉菜单鼠标滑过展开效果
2017/10/31 Javascript
JS常见错误(Error)及处理方案详解
2020/07/02 Javascript
Nuxt的路由配置和参数传递方式
2020/11/06 Javascript
微信小程序实现购物车功能
2020/11/18 Javascript
[01:04:06]DOTA2上海特级锦标赛A组资格赛#2 Secret VS EHOME第一局
2016/02/26 DOTA
[02:11]2016国际邀请赛中国区预选赛全程回顾
2016/07/01 DOTA
python中Genarator函数用法分析
2015/04/08 Python
Python搜索引擎实现原理和方法
2017/11/27 Python
Python基本socket通信控制操作示例
2019/01/30 Python
python3光学字符识别模块tesserocr与pytesseract的使用详解
2020/02/26 Python
社区版pycharm创建django项目的方法(pycharm的newproject左侧没有项目选项)
2020/09/23 Python
国外平面设计素材网站:The Hungry JPEG
2017/03/28 全球购物
Ibatis中如何提高SQL Map的性能
2013/05/11 面试题
为什么UNION ALL比UNION快
2016/03/17 面试题
租赁意向书范本
2014/04/01 职场文书
用人单位终止解除劳动合同证明书
2014/10/06 职场文书
学生党支部工作总结2015
2015/05/26 职场文书
2015暑期社会实践通讯稿
2015/07/18 职场文书
理解深度学习之深度学习简介
2021/04/14 Python
关于Redis的主从复制及哨兵问题
2022/06/16 Redis