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