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