PHP設(shè)計(jì)模式之適配器模式(Adapter)原理與用法詳解
本文實(shí)例講述了PHP設(shè)計(jì)模式之適配器模式(Adapter)原理與用法。分享給大家供大家參考,具體如下:
這個(gè)適配器模式,就是為了將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口,并且使用原本不兼容的而不能在一起工作的那些類可以在一起工作。它的核心思想就是把對某些相似的類的操作轉(zhuǎn)化為一個(gè)統(tǒng)一的“接口”(這里是比喻的說話)--適配器,或者比喻為一個(gè)“界面”,統(tǒng)一或屏蔽了那些類的細(xì)節(jié)。適配器模式還構(gòu)造了一種“機(jī)制”,使“適配”的類可以很容易的增減,而不用修改與適配器交互的代碼,符合“減少代碼間耦合”的設(shè)計(jì)原則。
我們來考慮下開發(fā)過程中,我們引用一個(gè)第三方類庫的場景,這個(gè)類庫隨著版本的改變,它提供的API也可能會(huì)改變。如果很不幸的是,你的應(yīng)用里引用的某個(gè)API已經(jīng)發(fā)生改變的時(shí)候,除了在心中默默地罵“wocao”之外,你還得去硬著頭皮去改大量的代碼,這個(gè)時(shí)候,為了減少工作量,我們就可以使用適配器模式。
先來看一個(gè)網(wǎng)上的案例:
- 假如我們原始的有一個(gè)UserInfo的類,提供用戶信息的類,早起設(shè)計(jì)該類的時(shí)候,只實(shí)現(xiàn)了一個(gè)getUserName獲取用戶名的方法。
 - 我們的MyOldObject類中,將從UserInfo這個(gè)類中獲取用戶信息,并且輸出用戶名
 - 隨著時(shí)間的推移,我們舊的UserInfo這個(gè)類只提供的獲取用戶名的方法,已經(jīng)沒法滿足需求,我們同時(shí)需要獲取用戶的年齡等信息。
 - 為了不改變原本UserInfo這個(gè)類,我們就繼承UserInfo,建立一個(gè)UserInfoAdapter類,實(shí)現(xiàn)getAge獲取年齡這樣的方法。
 - 在我們的MyNewObject新的類中,我們實(shí)例化UserInfoAdapter,打印出用戶姓名和年齡。
 - 這樣,隨著我們的擴(kuò)展,我們沒有改變原先UserInfo這個(gè)類和使用這個(gè)類的接口,我們通過適配的方法,將UserInfo類擴(kuò)展出來
 
代碼實(shí)現(xiàn)過程如下:
<?php
//早期的一個(gè)用戶類,只實(shí)現(xiàn)獲取用戶名的方法
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();
上述代碼是早期的時(shí)候,我們使用的案例。然而UserInfoAdapter類,隨著時(shí)間推移,項(xiàng)目需求在變化,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類無法滿足需求,需要調(diào)用UserInfoAdapter適配器這個(gè)類,如下:
<?php
include_once("UserInfoAdapter.php");
class MyNewObject {
    public function write() {
        $UserInfoAdapter = new UserInfoAdapter;
        print_r($UserInfoAdapter->getUser());
    }
}
$a = new MyNewObject;
$a->write();
大概了解了哈,接下來咱們通過一個(gè)故事來了解下。
開始的時(shí)候,黑棗玩具公司專門生產(chǎn)玩具,生產(chǎn)的玩具不限于狗、貓、獅子,魚等動(dòng)物,并且每個(gè)玩具都可以進(jìn)行“張嘴”與“閉嘴”操作,分別調(diào)用了openMouth與closeMouth方法。在這個(gè)時(shí)候,黑棗玩具公司的程序猿就定義一個(gè)抽象類Toy,甚至是接口Toy,完事其他的類去繼承父類,實(shí)現(xiàn)父類的方法,很和諧的是吧。
后來,為了擴(kuò)大業(yè)務(wù),也因?yàn)榧t棗遙控公司可以使用遙控設(shè)備對動(dòng)物進(jìn)行嘴巴控制,黑棗玩具公司打算與紅棗遙控公司合作。不過,麻煩的是,紅棗遙控公司的遙控設(shè)備是調(diào)用的動(dòng)物的doMouthOpen及doMouthClose方法。所以,黑棗玩具公司的程序員現(xiàn)在必須要做的是對Toy系列類進(jìn)行升級改造,使Toy能調(diào)用doMouthOpen及doMouthClose方法。
在考慮實(shí)現(xiàn)的方法時(shí),黑棗玩具公司的程序猿可以再在他們的父類子類里給紅棗遙控公司添加這么兩個(gè)方法就好啦。但是,當(dāng)黑棗玩具公司的程序猿一次又一次在父類子類里面重復(fù)添加著這兩個(gè)方法的時(shí)候,總會(huì)想著如此重復(fù)的工作,難道不能解決么?當(dāng)有數(shù)百個(gè)子類的時(shí)候,程序員會(huì)改瘋的。程序員往往比的是誰在不影響效率的時(shí)候更會(huì)“偷懶”,這樣做下去程序員會(huì)覺得自己很傻。
咱也不廢話了,先來看下最開始的時(shí)候的代碼:
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";
  }
}
完事,因?yàn)榫G棗遙控公司遙控設(shè)備更便宜穩(wěn)定,所以黑棗玩具公司又打算要與綠棗遙控公司合作。
不過綠棗遙控公司的遙控設(shè)備是調(diào)用的動(dòng)物的operMouth(type)方法來實(shí)現(xiàn)嘴巴控制。如果type)方法來實(shí)現(xiàn)嘴巴控制。如果type為0則“閉嘴”,反之張嘴。這下好了,程序員又得對Toy及其子類進(jìn)行升級,使Toy能調(diào)用operMouth()方法。
在這個(gè)時(shí)候,程序員必須要?jiǎng)幽X子想辦法了,就算自己勤快,萬一哪天紫棗青棗黃棗山棗這些遙控公司全來的時(shí)候,忽略自己不斷增多的工作量不說,這個(gè)Toy類可是越來越大,總有一天程序員不崩潰,系統(tǒng)也會(huì)崩潰的。
那么,問題出在哪里呢?
其實(shí)就是一開始的代碼設(shè)計(jì)實(shí)現(xiàn)違反了“開-閉”原則,也就是一個(gè)軟件實(shí)體應(yīng)當(dāng)對擴(kuò)展開放,對修改關(guān)閉。也就是說,在設(shè)計(jì)一個(gè)模塊的時(shí)候,應(yīng)當(dāng)使這個(gè)模塊可以在不被修改的前提下被擴(kuò)展。也就是說每個(gè)尸體都是一個(gè)小王國,你讓我參與你的事情這個(gè)可以,但你不能修改我的內(nèi)部,除非我的內(nèi)部代碼確實(shí)可以優(yōu)化。
來看下最后的結(jié)果:
<?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";
  }
}
//目標(biāo)角色:紅棗遙控公司
interface RedTarget
{
  public function doMouthOpen();
  public function doMouthClose();
}
//目標(biāo)角色:綠棗遙控公司及
interface GreenTarget
{
  public function operateMouth($type = 0);
}
//類適配器角色:紅棗遙控公司
class RedAdapter implements RedTarget
{
  private $adaptee;
  function __construct(Toy $adaptee)
  {
    $this->adaptee = $adaptee;
  }
  //委派調(diào)用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;
  }
  //委派調(diào)用Adaptee:GreenTarget的operateMouth方法
  public function operateMouth($type = 0)
  {
    if ($type) {
      $this->adaptee->openMouth();
    } else {
      $this->adaptee->closeMouth();
    }
  }
}
class testDriver
{
  public function run()
  {
     //實(shí)例化一只狗玩具
    $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();
大概了解了使用方式之后,我們來看下適配器模式之中的主要角色:
- 目標(biāo)(Target)角色:定義客戶端使用的與特定領(lǐng)域相關(guān)的接口,這也就是我們所期待得到的
 - 源(Adaptee)角色:需要進(jìn)行適配的接口
 - 適配器(Adapter)角色:對Adaptee的接口與Target接口進(jìn)行適配;適配器是本模式的核心,適配器把源接口轉(zhuǎn)換成目標(biāo)接口,此角色為具體類
 
使用場景如下:
   1、你想使用一個(gè)已經(jīng)存在的類,而它的接口不符合你的需求
   2、你想創(chuàng)建一個(gè)可以復(fù)用的類,該類可以與其他不相關(guān)的類或不可預(yù)見的類協(xié)同工作
   3、你想使用一個(gè)已經(jīng)存在的子類,但是不可能對每一個(gè)都進(jìn)行子類化以匹配它們的接口。對象適配器可以適配它的父類接口(僅限于對象適配器)
再來看下類適配器和對象適配器的一些解釋和區(qū)別:
類適配器:Adapter與Adaptee是繼承關(guān)系
   1、用一個(gè)具體的Adapter類和Target進(jìn)行匹配。結(jié)果是當(dāng)我們想要一個(gè)匹配一個(gè)類以及所有它的子類時(shí),類Adapter將不能勝任工作
   2、使得Adapter可以重定義Adaptee的部分行為,因?yàn)锳dapter是Adaptee的一個(gè)子集
   3、僅僅引入一個(gè)對象,并不需要額外的指針以間接取得adaptee
對象適配器:Adapter與Adaptee是委托關(guān)系
   1、允許一個(gè)Adapter與多個(gè)Adaptee同時(shí)工作。Adapter也可以一次給所有的Adaptee添加功能
   2、使用重定義Adaptee的行為比較困難
再來看下其它和適配器模式的對比:
- 橋梁模式(bridge模式):橋梁模式與對象適配器類似,但是橋梁模式的出發(fā)點(diǎn)不同,橋梁模式目的是將接口部分和實(shí)現(xiàn)部分分離,從而對它們可以較為容易也相對獨(dú)立的加以改變。而對象適配器模式則意味著改變一個(gè)已有對象的接口
 - 裝飾器模式(decorator模式):裝飾模式增強(qiáng)了其他對象的功能而同時(shí)又不改變它的接口。因此裝飾模式對應(yīng)用的透明性比適配器更好。
 
最后來看下類適配器和對象適配器案例,如下:
//類適配器使用的是繼承
<?php
/**
 * 目標(biāo)角色
 */
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方法,在此補(bǔ)充
  */
 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
/**
 * 目標(biāo)角色
 */
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;
 }
 /**
  * 委派調(diào)用Adaptee的sampleMethod1方法
  */
 public function sampleMethod1() {
  $this->_adaptee->sampleMethod1();
 }
 /**
  * 源類中沒有sampleMethod2方法,在此補(bǔ)充
  */
 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();
?>
好啦,本次記錄就到這里了。
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門教程》、《PHP數(shù)組(Array)操作技巧大全》、《PHP基本語法入門教程》、《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
希望本文所述對大家PHP程序設(shè)計(jì)有所幫助。
上一篇:Yii2框架中一些折磨人的坑
欄 目:PHP編程
下一篇:PHP防止sql注入小技巧之sql預(yù)處理原理與實(shí)現(xiàn)方法分析
本文標(biāo)題:PHP設(shè)計(jì)模式之適配器模式(Adapter)原理與用法詳解
本文地址:http://www.jygsgssxh.com/a1/PHPbiancheng/11023.html
您可能感興趣的文章
- 04-02關(guān)于txt數(shù)據(jù)庫php的信息
 - 04-02php本站才可以請求數(shù)據(jù) php本地?cái)?shù)據(jù)庫
 - 04-02網(wǎng)頁里php操作數(shù)據(jù)庫 php網(wǎng)頁例子
 - 04-02php打印請求數(shù)據(jù) php打印輸出結(jié)果
 - 04-02php數(shù)據(jù)庫地址 phpstudy 數(shù)據(jù)庫
 - 04-02php插入數(shù)據(jù)庫為亂碼 php連接數(shù)據(jù)庫亂碼
 - 04-02php數(shù)據(jù)庫數(shù)據(jù)相加 php數(shù)據(jù)庫添加數(shù)據(jù)語句
 - 04-02php數(shù)據(jù)庫輸入變量 php里輸出數(shù)據(jù)庫數(shù)據(jù)函數(shù)
 - 04-02數(shù)據(jù)權(quán)限架構(gòu)思路php 數(shù)據(jù)權(quán)限設(shè)計(jì)方案
 - 04-02php如何用導(dǎo)入數(shù)據(jù) php用來導(dǎo)入其他文件的語句
 


閱讀排行
本欄相關(guān)
- 04-02php本站才可以請求數(shù)據(jù) php本地?cái)?shù)據(jù)庫
 - 04-02關(guān)于txt數(shù)據(jù)庫php的信息
 - 04-02php打印請求數(shù)據(jù) php打印輸出結(jié)果
 - 04-02網(wǎng)頁里php操作數(shù)據(jù)庫 php網(wǎng)頁例子
 - 04-02php插入數(shù)據(jù)庫為亂碼 php連接數(shù)據(jù)庫亂
 - 04-02php數(shù)據(jù)庫地址 phpstudy 數(shù)據(jù)庫
 - 04-02php數(shù)據(jù)庫數(shù)據(jù)相加 php數(shù)據(jù)庫添加數(shù)據(jù)
 - 04-02數(shù)據(jù)權(quán)限架構(gòu)思路php 數(shù)據(jù)權(quán)限設(shè)計(jì)方
 - 04-02php數(shù)據(jù)庫輸入變量 php里輸出數(shù)據(jù)庫數(shù)
 - 04-02php如何用導(dǎo)入數(shù)據(jù) php用來導(dǎo)入其他文
 
隨機(jī)閱讀
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
 - 08-05DEDE織夢data目錄下的sessions文件夾有什
 - 01-11ajax實(shí)現(xiàn)頁面的局部加載
 - 01-10delphi制作wav文件的方法
 - 04-02jquery與jsp,用jquery
 - 01-10C#中split用法實(shí)例總結(jié)
 - 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
 - 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
 - 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
 - 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
 


