适配器(Adapter)模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。
适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。

适配器模式的结构

类适配器

  1. 类适配器不需要封装任何对象, 因为它同时继承了客户端和服务的行为。 适配功能在重写的方法中完成。 最后生成的适配器可替代已有的客户端类进行使用。由于php只支持单继承,所以我们可以使用继承+实现的方式达到同样的效果。

对象适配器

  1. 客户端 (Client) 是包含当前程序业务逻辑的类。
  2. 客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。
  3. 服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能
  4. 适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。
  5. 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。

示例

假设现在有一个企业网站在同时销售软件服务和软件产品,目前只支持人民币来完成计算,现在由于新增了海外客户,现在开发者希望能有一个转换器能处理人民币与美元的兑换。而不要改变原来的人民币计算交易金额的类。通过增加一个适配器可以让我们的程序可以用人民币计算也可以用美元计算。

类适配器实现

代码示例:
MoneyCalc.php

<?php
namespace DesignPatterns\Adapter\example1;
class MoneyCalc
{
    private $money;//金额 默认人民币
    private $product;//软件产品
    private $service;//软件服务
    public $rate = 1;//兑换率

    public function requestCalc($productNow,$serviceNow)
    {
        $this->product = $productNow;
        $this->service = $serviceNow;
        $this->money = $this->product + $this->service;
        return $this->requestTotal();

    }
    public function requestTotal()
    {
        $this->money *= $this->rate;
        return $this->money;
    }
}

创建美元适配器,使系统可以支持美元。首先我们要创建一个接口,接口名为ITarget,接口内有一个抽象方法requester().

ITarget.php

<?php
namespace DesignPatterns\Adapter\example1;
interface ITarget
{
    public function requester();
}

适配器(Adapter)需要实现ITarget接口与MoneyCalc具体类,创建DollarCalcAdapter不需要太多的工作,我们大部分的工作已经在MoneyCalc类中实现,所以我们只需实现 requester方法让它能把人民币转化为美元。

DollarCalcAdapter.php

<?php
namespace DesignPatterns\Adapter\example1;

class DollarCalcAdapter extends MoneyCalc implements ITarget
{
    public function __construct()
    {
        $this->requester();
    }

    public function requester()
    {
       $this->rate = 0.141;
       return $this->rate;
    }
}

运行 Client.php

<?php
namespace DesignPatterns\Adapter\example1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        //默认人民币
        $moneyCalc = new MoneyCalc();
        echo 'rmb ¥'.$moneyCalc->requestCalc(40,50);
        echo '<br>';
        //美元金额
        $dollarCalc = new DollarCalcAdapter();
        echo "dollar :$".$dollarCalc->requestCalc(40,50);;

    }
}
$worker = new Client();
$worker->run();

运行结果:

对象适配器实现

代码示例:
MoneyCalc.php

<?php
namespace DesignPatterns\Adapter\example1;
class MoneyCalc
{
    private $money;//金额 默认人民币
    private $product;//软件产品
    private $service;//软件服务
    public $rate = 1;//兑换率

    public function requestCalc($productNow,$serviceNow)
    {
        $this->product = $productNow;
        $this->service = $serviceNow;
        $this->money = $this->product + $this->service;
        return $this->requestTotal();

    }
    public function requestTotal()
    {
        $this->money *= $this->rate;
        return $this->money;
    }
}

ITarget.php

<?php
namespace DesignPatterns\Adapter\example2;
interface ITarget
{
    public function requestCalc($productNow,$serviceNow);
}

适配器 DollarCalcAdapter.php

<?php
namespace DesignPatterns\Adapter\example2;

class DollarCalcAdapter implements ITarget
{
    private $moneyCalc;
    public function __construct(MoneyCalc $moneyCalc)
    {
        $this->moneyCalc = $moneyCalc;
        $this->moneyCalc->rate = 0.141;
    }

    public function requestCalc($productNow,$serviceNow)
    {
        return $this->moneyCalc->requestCalc($productNow,$serviceNow);
    }
}

运行 Client.php

<?php
namespace DesignPatterns\Adapter\example2;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        //默认人民币
        $moneyCalc = new MoneyCalc();
        echo 'rmb ¥'.$moneyCalc->requestCalc(40,50);
        echo '<br>';
        //美元金额
        $dollarCalc = new DollarCalcAdapter($moneyCalc);
        echo "dollar :$".$dollarCalc->requestCalc(40,50);;

    }
}
$worker = new Client();
$worker->run();

运行结果:

类适配器和对象适配器如何选择

针对类适配器和对象适配器两种实现方式,在实际的开发中,我们应该选择使用哪一种呢?判断的标准主要有两个,一个是 Adaptee(被适配类 如例子中的MoneyCalc类) 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。

  • 如果 Adaptee 接口并不多,那两种实现方式都可以。
  • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
  • 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。

代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。

  • 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
  • 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
  • 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
  • 适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

github示例:https://github.com/yangpanyao/design-patterns/tree/master/Adapter

Last modification:June 24th, 2020 at 05:41 pm