本文同步更新於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类别图说明。)
ʕ •ᴥ•ʔ:希望这个范例有浅显易懂。
<<: Day 20 - 天眼CNN 的耳朵和嘴巴 - RNN(1) -传统RNN
>>: [JS] You Don't Know JavaScript [this & Object Prototypes] - Prototypes [下]
昨天介绍完SMO算法第三步,今天就要来写这个方法第四步, 昨天我们得到aj,接下来要使用aj来更新a...
今天来实作节庆详情页面! 昨天有讲到我是用router-link query的方式把参数带到URL中...
Values 有写过任何一门程序语言的应该都知道,对於每个变数的值来说都会有其对应的资料型态,而在S...
今天进度 鸟哥私房菜 - 第十五章、例行性工作排程(crontab) 我在 Crontab.guru...
钓鱼游戏 教学原文参考:钓鱼游戏 这篇文章会介绍,如何在 Scratch 3 里使用多个角色、函式、...