本文同步更新於blog
情境:玉皇大帝要舉辦渡河比賽,動物選手各顯神通。
<?php
namespace App\BridgePattern\Zodiac;
class Program
{
/**
* @param string $animal
*/
public function crossRiver($animal)
{
switch ($animal) {
case 'rat':
echo '悠哉地站啊站';
break;
case 'ox':
echo '努力地游啊游';
break;
case 'dragon':
echo '壯麗地飛啊飛';
break;
case 'snake':
echo '迅速地滑啊滑';
break;
}
}
}
故事中,老鼠與貓站在水牛的背上。
水牛勤奮地游,龍翱翔於天際,
蛇則獨樹一幟地滑行在水面⋯⋯
但今天這都不是重點。
假如原有的動物選手要改變渡河方式?(比如貓貓決定自己游泳)
假如要新增新的動物選手?(比如老虎也要參賽)
渡河方式與動物選手是兩種不同層級的職責。
讓我們用橋接模式改寫它。
需求一:渡河方式
<?php
namespace App\BridgePattern\Zodiac\Contracts;
interface CrossRiverBehavior
{
public function crossRiver();
}
<?php
namespace App\BridgePattern\Zodiac\CrossRiverBehaviors;
use App\BridgePattern\Zodiac\Contracts\CrossRiverBehavior;
class RideAtopTheOx implements CrossRiverBehavior
{
public function crossRiver()
{
echo '悠哉地站啊站';
}
}
<?php
namespace App\BridgePattern\Zodiac\CrossRiverBehaviors;
use App\BridgePattern\Zodiac\Contracts\CrossRiverBehavior;
class Swim implements CrossRiverBehavior
{
public function crossRiver()
{
echo '努力地游啊游';
}
}
<?php
namespace App\BridgePattern\Zodiac\CrossRiverBehaviors;
use App\BridgePattern\Zodiac\Contracts\CrossRiverBehavior;
class FlyWithNoWings implements CrossRiverBehavior
{
public function crossRiver()
{
echo '壯麗地飛啊飛';
}
}
<?php
namespace App\BridgePattern\Zodiac\CrossRiverBehaviors;
use App\BridgePattern\Zodiac\Contracts\CrossRiverBehavior;
class Slither implements CrossRiverBehavior
{
public function crossRiver()
{
echo '迅速地滑啊滑';
}
}
需求二:動物選手
<?php
namespace App\BridgePattern\Zodiac\Abstracts;
use App\BridgePattern\Zodiac\Contracts\CrossRiverBehavior;
abstract class Contestant
{
/**
* @var CrossRiverBehavior
*/
protected $crossRiverBehavior;
public function crossRiver()
{
$this->crossRiverBehavior->crossRiver();
}
}
此處的crossRiver()並沒有具體行為,
而是交由渡河方式來實作!
<?php
namespace App\BridgePattern\Zodiac\Contestants;
use App\BridgePattern\Zodiac\CrossRiverBehaviors\RideAtopTheOx;
use App\BridgePattern\Zodiac\Abstracts\Contestant;
class Rat extends Contestant
{
/**
* @var RideAtopTheOx
*/
protected $crossRiverBehavior;
public function __construct()
{
$this->crossRiverBehavior = new RideAtopTheOx();
}
}
<?php
namespace App\BridgePattern\Zodiac\Contestants;
use App\BridgePattern\Zodiac\CrossRiverBehaviors\Swim;
use App\BridgePattern\Zodiac\Abstracts\Contestant;
class Ox extends Contestant
{
/**
* @var Swim
*/
protected $crossRiverBehavior;
public function __construct()
{
$this->crossRiverBehavior = new Swim();
}
public function crossRiver()
{
$this->crossRiverBehavior->crossRiver();
}
}
<?php
namespace App\BridgePattern\Zodiac\Contestants;
use App\BridgePattern\Zodiac\CrossRiverBehaviors\FlyWithNoWings;
use App\BridgePattern\Zodiac\Abstracts\Contestant;
class Dragon extends Contestant
{
/**
* @var FlyWithNoWings
*/
protected $crossRiverBehavior;
public function __construct()
{
$this->crossRiverBehavior = new FlyWithNoWings();
}
public function crossRiver()
{
$this->crossRiverBehavior->crossRiver();
}
}
<?php
namespace App\BridgePattern\Zodiac\Contestants;
use App\BridgePattern\Zodiac\CrossRiverBehaviors\Slither;
use App\BridgePattern\Zodiac\Abstracts\Contestant;
class Snake extends Contestant
{
/**
* @var Slither
*/
protected $crossRiverBehavior;
public function __construct()
{
$this->crossRiverBehavior = new Slither();
}
}
<?php
namespace App\BridgePattern\Zodiac;
use ReflectionClass;
use App\BridgePattern\Zodiac\Contracts\Contestant;
class Program
{
/**
* @param string $animalName
*/
public function crossRiver($animalName)
{
$contestant = $this->getContestant($animalName);
$contestant->crossRiver();
}
/**
* @param string $animalName
* @return Contestant
*/
private function getContestant($animalName)
{
$namespace = 'App\BridgePattern\Zodiac\Contestants';
$className = ucfirst($animalName);
$reflector = new ReflectionClass($namespace . '\\' . $className);
return $reflector->newInstance();
}
}
運用反射 (Reflection) 機制,讓客戶端的程式碼不再修改。
[單一職責原則]
我們把渡河方式與動物選手視作兩種不同的職責。
[開放封閉原則]
無論新增動物選手或者修改渡河方式,皆不會改動到所有程式碼。
[介面隔離原則]
區分了渡河方式介面與動物選手介面。
雖然兩者目前都只有 crossRiver() 方法,但實作的目的不同。
日後也可能因需求調整介面,而發展出截然不同的形式。
[依賴反轉原則]
客戶端的程式碼依賴於動物選手介面。
動物選手介面依賴於渡河方式介面。
再由各個實體動物選手與實體渡河方式進行實作。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:希望這個範例有淺顯易懂。