欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-30 10:20:52
本文实例讲述了php设计模式之适配器模式(adapter)原理与用法。分享给大家供大家参考,具体如下: 这个适配器模式,就是为了将一个类的接口转换成客户希望的另外一个接口,并且使用原...

本文实例讲述了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();
?>

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