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