适配器很容易理解, 大多数人家庭都有手机转接器, 用来为移动电话充电,这就是一种适配器. 如果只有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.我不认为交换等同于适配器, 不过道理是一样的, 在适配器模式中, 原来的代码没有任何改变, 有变化的只是适配器.
如果需要结合使用两个不兼容的接口, 这种情况下, 适配器模式最适用.适配器可以完成接口的"联姻".可以把适配器看作是一个婚姻顾问;通过创建一个公共接口来克服双方的差异.利用 这种设计模式, 可以促成二者的合作,而避免完全重写某一部分.

转载于:https://www.cnblogs.com/wntd/p/9628391.html

设计模式之:适配器模式相关推荐

  1. C#设计模式(7)——适配器模式(Adapter Pattern)

    一.引言 在实际的开发过程中,由于应用环境的变化(例如使用语言的变化),我们需要的实现在新的环境中没有现存对象可以满足,但是其他环境却存在这样现存的对象.那么如果将"将现存的对象" ...

  2. 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)

    [索引页] [源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabcd 介绍 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本 ...

  3. 跟JBPM学设计模式之适配器模式

    跟JBPM学设计模式之适配器模式 模式简介 适配器模式(Adapter),将一个类的接口转换成客户希望的另一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 说起适配器模式 ...

  4. python适配器模式角色_Python设计模式之适配器模式原理与用法详解

    本文实例讲述了Python设计模式之适配器模式原理与用法.分享给大家供大家参考,具体如下: 适配器模式(Adapter Pattern):将一个类的接口转换成为客户希望的另外一个接口. 下面是一个适配 ...

  5. 【设计模式】—— 适配器模式Adapter

    模式意图 如果已经有了一种类,而需要调用的接口却并不能通过这个类实现.因此,把这个现有的类,经过适配,转换成支持接口的类. 换句话说,就是把一种现有的接口编程另一种可用的接口. 模式结构 [类的适配器 ...

  6. 设计模式适配器模式_21世纪的设计模式:适配器模式

    设计模式适配器模式 这是我的演讲的第三部分," 21世纪的设计模式" . 适配器模式桥接世界. 在一个世界中,我们有一个概念的界面. 在另一个世界,我们有不同的界面. 这两个接口有 ...

  7. Java面试题:单例设计模式、适配器模式的不同方式

    QUESTION:单例设计模式.适配器模式的不同方式? ANSWER: 1.单例设计模式,适配器设计模式     单利设计模式:             在java中,单例模式是指为了保证类在内存中只 ...

  8. [学习笔记]设计模式[6]-{适配器模式外观模式}

    设计原则 最少知识原则:只和你的密友谈话 这个原则的意思是,在系统设计的过程中,不要让太多的类耦合在一起,免得对系统一部分的修改会影响到其他部分.在设计系统之前,应该首先注意对象与对象之间的交互关系, ...

  9. 适配器模式_21世纪的设计模式:适配器模式

    适配器模式 这是我的演讲的第三部分," 21世纪的设计模式" . 适配器模式桥接世界. 在一个世界中,我们有一个概念的界面: 在另一个世界中,我们有不同的界面. 这两个接口有不同的 ...

  10. 设计模式回顾——适配器模式(C++)

    文章目录 1 前言 2 什么是适配器模式 2.1 生活中的适配器 2.2 适配器模式含义 2.3 适配器模式作用 3 适配器模式优缺点 3.1 适配器优缺点 3.2 类适配器优缺点 3.3 对象适配器 ...

最新文章

  1. Mysql报错Forcing close of thread 139 user: 'root'
  2. 《github一天一道算法题》:插入排序
  3. arcgis 点坐标python_Python写的arcgis中点坐标txt转面状shp的小程序
  4. Day 9 函数的初识1
  5. 基础算法 —— 排序算法
  6. 从程序员到项目经理(六):程序员加油站 -- 懂电脑更要懂人脑
  7. 雪城大学信息安全讲义 七、格式化字符串漏洞
  8. 2016.08.15
  9. Qtableview实现对某一列可编辑,对某几列不可编辑
  10. C++“准”标准库Boost学习指南(3):Boost.Utility
  11. 3D建模自学要学多久?
  12. LeetCode 661. Image Smoother
  13. matlab编程999玫瑰花,网上收到的用matlab画玫瑰花的代码怎么不行啊,报告错误,求大神...
  14. bootstrap国内cdn库
  15. 爱否赢了?华为拍月亮方法已申请专利
  16. 声明对象和实例化对象的区别
  17. 题解 2360: [信息学奥赛一本通-T1449]魔板
  18. 【FFMPEG使用经验一】FFMPEG解码MP4帧数不对问题探索
  19. MATLAB怎么解方程解,怎么用Matlab解方程?
  20. 苹果库乐队怎么玩_苹果安卓手机最简单换铃声方法,无需电脑,简单一步就能换成流行歌曲...

热门文章

  1. [Machine Learning]kNN代码实现(Kd tree)
  2. Java中Byte类型数据在运算中的问题
  3. 给ModalPopupExtender控件添加弹出关闭等事件
  4. 图像边缘检测技术与理论发展脉络梳理大放送
  5. WPF模板(二)应用
  6. Python通过future处理并发
  7. 根文件系统挂载过程分析
  8. 无线路由器说说2.4G和5G Wi-Fi的区别
  9. JAVA中字符串连接效率的测试(续)
  10. linux 3.0 kernel