PHP设计模式之适配器模式定义与用法详解


Posted in PHP onApril 03, 2018

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

适配器很容易理解, 大多数人家庭都有手机转接器, 用来为移动电话充电,这就是一种适配器. 如果只有USB接头, 就无法将移动电话插到标准插座上. 实际上, 必须使用一个适配器, 一端接USB插头, 一端接插座. 当然, 你可以拿出电气工具,改装USB连接头, 或者重新安装插座, 不过这样会带来很多额外的工作, 而且可能会把连接头或插座弄坏. 所以, 最可取的方法就是找一个适配器. 软件开发也是如此.

类适配器模式(使用继承)

类适配器模式很简单, 不过与对象适配器模式相比, 类适配器模式的灵活性弱些, 类适配器简单的原因在于 , 适配器(Adapter)会从被适配者(Adaptee)继承功能, 所以适配模式中需要编写的代码比较少.

由于类适配器模式包含双重继承, 但是PHP并不支持双重继承, 不过幸运的是,PHP可以用接口来模拟双重继承, 下面是一个正确的结构, 不仅继承了一个类, 同时还继承了一个接口

class ChildClass extends ParentClass implements ISomeAdapter
{
}

实现类适配器模式时, 参与者必须包括一个PHP接口

下面以一个货币兑换为例来演示:

假设有一个企业网站在同时销售软件服务和软件产品, 目前, 所有交易都在美国进行, 所以完全可以用美元来完成所有计算.现在开发人员希望能有一个转换器能处理美元和欧元的兑换, 而不改变原来按美元交易额的类.通过增加一个适配器, 现在程序即可以用美元计算也可以用欧元计算.

DollarCalc.php

<?php
class DollarCalc
{
 private $dollar;
 private $product;
 private $service;
 public $rate = 1;
 public function requestCalc($productNow, $serviceNow)
 {
  $this->product = $productNow;
  $this->service = $serviceNow;
  $this->dollar = $this->product + $this->service;
  return $this->requestTotal();
 }
 public function requestTotal()
 {
  $this->dollar *= $this->rate;
  return $this->dollar;
 }
}

查看这个类,可以看到其中有一个属性$rate,requestTotal()方法使用$rate计算一次交易的金额.在这个版本中, 这个值设置为1,实际上总金额无需再乖以兑换率, 不过如果要为客户提供折扣或者要增加额外服务或产品的附加费, $rate变量会很方便. 这个类并不是适合器模式的一部分, 不过这是一个起点.

需求变化了

现在客户的公司要向欧洲发展,所以需要开发一个应用, 能够用欧元完成同样的计算. 你希望这个欧元计算能够像DollarCalc一样, 所要做的就是改变变量名.

EuroCalc.php

<?php
class EuroCalc
{
 private $euro;
 private $product;
 private $service;
 public $rate = 1;
 public function requestCalc($productNow, $serviceNow)
 {
  $this->product = $productNow;
  $this->service = $serviceNow;
  $this->euro = $this->product + $this->service;
  return $this->requestTotal();
 }
 public function requestTotal()
 {
  $this->euro *= $this->rate;
  return $this->euro;
 }
}

接下来, 再把应用的其余部分插入到EuroCalc类中. 不过,因为客户的所有数据都是按美元计算的.换句话说, 如果不重新开发整个程序, 就无法在系统中"插入"这个欧元计算. 但是你不想这么做. 为了加入EuroCalc, 你需要一个适配器: 就像找一个适配器来适应欧洲的插座一样, 可以创建一个适配器, 使你的系统能够使用欧元. 幸运的是, 类适配器正是为这样的情况设计的.首先需要创建一个接口. 在这个类图中, 这个接口名为ITarget. 它只有一个方法requester(). requester()是一个抽象方法, 要由接口的具体实现来实现这个方法.

ITarget.php

<?php
interface ITarget
{
 public function requester();
}

现在开发人员可以实现requester()方法, 请求欧元而不是美元.

在使用继承的适配器设计模式中, 适配器(Adapter)参与都既实现ITarget接口,还实现了具体类EuroCalc. 创建EuroAdapter不需要做太多工作, 因为大部分工作已经在EuroCal类中完成.现在要做的就是实现request()方法, 使它能把美元值转换为欧元值.

EuroAdapter.php

<?php
include_once('EuroCalc.php');
include_once('ITarget.php');
class EuroAdapter extends EuroCalc implements ITarget
{
 public function __construct()
 {
  $this->requester();
 }
 public function requester()
 {
  $this->rate = 0.8111;
  return $this->rate;
 }
}

类适配模式中, 一个具体类会继承另一个具体类, 有这种结构的设计模式很少见, 大多数设计模式中, 几乎都是继承一个抽象类, 并由类根据需要实现其抽象方法和属性. 换句话说, 一般谈到继承时, 都是具体类继承抽象类.

由于既实现了一个接口又扩展了一个类, 所以EuroAdapter类同时拥有该接口和具体类的接口. 通过使用requester()方法, EuroAdapter类可以设置rate值(兑换率), 从而能使用被适配者的功能, 而元而做任何改变.

下面定义一个Client类, 从EuroAdapter和DollarCalc类发出请求. 可以看到,原来的DollarCalc仍能很好地工作, 不过它没有ITarget接口.

Client.php

<?php
include_once('EuroAdapter.php');
include_once('DollarCalc.php');
class Client
{
 public function __construct()
 {
  $euro = '?';
  echo "区元: $euro" . $this->makeApapterRequest(new EuroAdapter()) . '<br />';
  echo "美元: $: " . $this->makeDollarRequest(new DollarCalc()) . '<br />';
 }
 private function makeApapterRequest(ITarget $req)
 {
  return $req->requestCalc(40,50);
 }
 private function makeDollarRequest(DollarCalc $req)
 {
  return $req->requestCalc(40,50);
 }
}
$woker = new Client();

运行结果如下:

Euros: ?72.999
Dollars: $: 90

可以看到,美元和欧元都可以处理, 这就是适配器模式的方便之处.

这个计算很简单, 如果是针对更为复杂的计算, 继承要提供建立类适配器的Target接口的必要接口和具体实现

使用组合的适配器模式

对象适配器模式使用组合而不是继承, 不过它也会完成同样的目标. 通过比较这两个版本的适配器模式, 可以看出它们各自的优缺点. 采用类适配器模式时,适配器可以继承它需要的大多数功能, 只是通过接口稍微调. 在对象适配器模式中 适配器(Adapter)参与使用被适配者(Adaptee), 并实现Target接口. 在类适配器模式中, 适配器(Adapter)则是一个被适配者(Adaptee), 并实现Target接口.

示例: 从桌面环境转向移动环境

PHP程序员经常会遇到这样一个问题:需要适应移动环境而做出调整.不久之前,你可能只需要考虑提供一个网站来适应多种不同的桌面环境. 大多数桌面都使用一个布局, 再由设计人员让它更美观. 对于移动设备, 设计人员和开发人员不仅需要重新考虑桌面和移动环境中页面显示的设计元素, 还要考虑如何从一个环境切换到另一个环境.

首先来看桌面端的类Desktop(它将需要一个适配器). 这个类使用了一个简单但很宽松的接口:

IFormat.php

<?php
interface IFormat
{
 public function formatCSS();
 public function formatGraphics();
 public function horizontalLayout();
}

它支持css和图片选择, 不过其中一个方法指示一种水平布局, 我们知道这种布局并不适用小的移动设备.下面给出实现这个接口的Desktop类

Desktop.php

<?php
include_once('IFormat.php');
class Desktop implements IFormat
{
 public function formatCSS()
 {
  echo "引用desktop.css<br />";
 }
 public function formatGraphics()
 {
  echo "引用desktop.png图片<br />";
 }
 public function horizontalLayout()
 {
  echo '桌面:水平布局';
 }
}

问题来了, 这个布局对于小的移动设备来说太宽了. 所以我们的目标是仍采用同样的内容, 但调整为一种移动设计.

下面来看移动端的类Mobile

首先移动端有一个移动端的接口

IMobileFormat

<?php
interface IMobileFormat
{
 public function formatCSS();
 public function formatGraphics();
 public function verticalLayout();
}

可以看到, IMobileFormat接口和IFormat接口是不一样的,也就是不兼容的, 一个包含了方法horizontalLayout(), 另一个包含方法verticalLaout(), 它们的差别很小, 最主要的区别是: 桌面设计可以采用水平的多栏布局, 而移动设计要使用垂直布局,而适配器就是要解决这个问题

下面给出一个实现了IMoibleFormat接口的Mobile类

Mobile.php

<?php
include_once('IMobileFormat.php');
class Mobile implements IMobileFormat
{
 public function formatCSS()
 {
  echo "引用mobile.css<br />";
 }
 public function formatGraphics()
 {
  echo "引用mobile.png图片<br />";
 }
 public function verticalLayout()
 {
  echo '移动端:垂直布局';
 }
}

Mobile类和Desktop类非常相似, 不过是图片和CSS引用不同

接下来,我们需要一个适配器,将Desktop和Mobile类结合在一起

MobileAdapter.php

<?php
include_once('IFormat.php');
include_once('Mobile.php');
class MobileAdapter implements IFormat
{
 private $mobile;
 public function __construct(IMobileFormat $mobileNow)
 {
  $this->mobile = $mobileNow;
 }
 public function formatCSS()
 {
  $this->mobile->formatCSS();
 }
 public function formatGraphics()
 {
  $this->mobile->formatGraphics();
 }
 public function horizontalLayout()
 {
  $this->mobile->verticalLayout();
 }
}

可以看到,MobileAdapter实例化时要提供一个Mobile对象实例.还要注意 ,类型提示中使用了IMobileFormat, 确保参数是一个Mobile对象.有意思的是, Adapter参与者通过实现horizontalLayout()方法来包含verticalLayout()方法.实际上, 所有MobileAdapter方法都包装了一个Mobile方法.碰巧的是, 适配器参与者中的一个方法并不在适配器接口中(verticalLayout());它们可能完全不同, 适配器只是把它们包装在适配器接口(IFormat)的某一方法中.

客户调用(Client)

Client.php

<?php
include_once('Mobile.php');
include_once('MobileAdapter.php');
class Client
{
 private $mobile;
 private $mobileAdapter;
 public function __construct()
 {
  $this->mobile = new Mobile();
  $this->mobileAdapter = new MobileAdapter($this->mobile);
  $this->mobileAdapter->formatCSS();
  $this->mobileAdapter->formatGraphics();
  $this->mobileAdapter->horizontalLayout();
 }
}
$worker = new Client();

适配器模式中的Client类必须包装Adaptee(Mobile)的一个实例, 以便集成到Adapter本身.实例化Adapter时, Client使用Adatee作为参数来完成Adapter的实例化.所以客户必须首先创建一个Adapter对象(new Mobile()), 然后创建一个Adapter((new MobileAdapter($this->mobile)).

Client类的大多数请求都是通过MobileAdapter发出的. 不过这个代码的最后他使用了Mobile类的实例.

适配器和变化

PHP程序员要即该面对变化.不同版本的PHP会变化, 可能增加新的功能, 另外还可能取消一些功能.而且随着PHP的大大小小的变化,MySQL也在改变.例如, mysql的扩展包升级为mysqli, PHP开发人员需要相应调整, 要改为使用mysqli中的新API.这里适合采用适配器模式吗?可能不适合.适配器可能适用, 可能不适用,这取决于你的程序如何配置.当然可以重写所有连接和交互代码, 不过这可不是适配器模式的本意, 这就像是重新安装USB连接头, 想把它插进标准的墙上插座一样. 不过, 如果所有原来的mysql代码都在模块中, 你可以修改这个模块(类),换入一个有相同接口的新模块.只是要使用mysqli而不是mysql.我不认为交换等同于适配器, 不过道理是一样的, 在适配器模式中, 原来的代码没有任何改变, 有变化的只是适配器.

如果需要结合使用两个不兼容的接口, 这种情况下, 适配器模式最适用.适配器可以完成接口的"联姻".可以把适配器看作是一个婚姻顾问;通过创建一个公共接口来克服双方的差异.利用 这种设计模式, 可以促成二者的合作,而避免完全重写某一部分.

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

PHP 相关文章推荐
PHP调用三种数据库的方法(3)
Oct 09 PHP
我的论坛源代码(二)
Oct 09 PHP
PHP新手上路(五)
Oct 09 PHP
php 获取百度的热词数据的代码
Feb 18 PHP
ajax返回值中有回车换行、空格的解决方法分享
Oct 24 PHP
php遍历目录输出目录及其下的所有文件示例
Jan 27 PHP
PHP经典面试题集锦
Mar 19 PHP
PHP响应post请求上传文件的方法
Dec 17 PHP
php ajax实现文件上传进度条
Mar 29 PHP
如何正确配置Nginx + PHP
Jul 15 PHP
yii框架使用分页的方法分析
Jul 25 PHP
PHP常用字符串输出方法分析(echo,print,printf及sprintf)
Mar 09 PHP
PHP延迟静态绑定的深入讲解
Apr 02 #PHP
PHP设计模式之装饰器模式定义与用法详解
Apr 02 #PHP
PHP设计模式之状态模式定义与用法详解
Apr 02 #PHP
PHP设计模式之模板方法模式定义与用法详解
Apr 02 #PHP
PHP实现动态获取函数参数的方法示例
Apr 02 #PHP
PHP调用其他文件中的类
Apr 02 #PHP
为何说PHP引用是个坑,要慎用
Apr 02 #PHP
You might like
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
收听短波不可能有声音清晰的品质吗
2021/03/01 无线电
php使用base64加密解密图片示例分享
2014/01/20 PHP
帝国cms常用标签汇总
2015/07/06 PHP
Symfony2之session与cookie用法小结
2016/03/18 PHP
如何使用PHP给图片加水印
2016/10/12 PHP
PHP实现的方程求解示例分析
2016/11/11 PHP
tp5框架使用composer实现日志记录功能示例
2019/01/10 PHP
jquery使用淘宝接口跨域查询手机号码归属地实例
2013/11/28 Javascript
javascript判断chrome浏览器的方法
2014/03/26 Javascript
js单独获取一个checkbox看其是否被选中
2014/09/22 Javascript
node.js下when.js 的异步编程实践
2014/12/03 Javascript
Bootstrap每天必学之警告框插件
2016/04/26 Javascript
前端实现文件的断点续传(前端文件提交+后端PHP文件接收)
2016/11/04 Javascript
微信小程序 toast 详解及实例代码
2016/11/09 Javascript
原生js中ajax访问的实例详解
2017/09/19 Javascript
BootStrap Validator 根据条件在JS中添加或移除校验操作
2017/10/12 Javascript
JavaScript中.min.js和.js文件的区别讲解
2019/02/13 Javascript
详解async/await 异步应用的常用场景
2019/05/13 Javascript
JS字符串补全方法padStart()和padEnd()
2020/05/27 Javascript
vuecli项目构建SSR服务端渲染的实现
2020/10/30 Javascript
[52:06]完美世界DOTA2联赛决赛日 Inki vs LBZS 第一场 11.08
2020/11/10 DOTA
Python开发WebService系列教程之REST,web.py,eurasia,Django
2014/06/30 Python
python实现对一个完整url进行分割的方法
2015/04/29 Python
深入理解Python中装饰器的用法
2016/06/28 Python
关于Python面向对象编程的知识点总结
2017/02/14 Python
Pycharm 2019 破解激活方法图文详解
2019/10/11 Python
详解有关PyCharm安装库失败的问题的解决方法
2020/02/02 Python
Python测试框架:pytest学习笔记
2020/10/20 Python
wedgwood加拿大官网:1759年成立的英国国宝级陶瓷餐具品牌
2018/07/17 全球购物
大学生未来职业生涯规划书
2014/02/15 职场文书
大学计划书范文800字
2014/08/14 职场文书
共青团员自我评价范文
2014/09/14 职场文书
2014年团委工作总结
2014/11/13 职场文书
2015年英语教研组工作总结
2015/05/23 职场文书
解决sql server 数据库,sa用户被锁定的问题
2021/06/11 SQL Server