範例:十二生肖

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
{
/**
* @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;
}
}
}

故事中,老鼠與貓站在水牛的背上。

水牛勤奮地游,龍翱翔於天際,
蛇則獨樹一幟地滑行在水面⋯⋯

但今天這都不是重點。

假如原有的動物選手要改變渡河方式?(比如貓貓決定自己游泳)
假如要新增新的動物選手?(比如老虎也要參賽)

渡河方式與動物選手是兩種不同層級的職責。
讓我們用橋接模式改寫它。


需求一:渡河方式

  • 首先定義渡河方式的介面
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
{
/**
* @var CrossRiverBehavior
*/
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
{
/**
* @var RideAtopTheOx
*/
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
20
21
22
23
24
<?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();
}
}
  • 動物選手:龍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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();
}
}
  • 動物選手:蛇
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
{
/**
* @var Slither
*/
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
{
/**
* @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() 方法,但實作的目的不同。
日後也可能因需求調整介面,而發展出截然不同的形式。

[依賴反轉原則]
客戶端的程式碼依賴於動物選手介面。
動物選手介面依賴於渡河方式介面。
再由各個實體動物選手與實體渡河方式進行實作。


ʕ •ᴥ•ʔ:希望這個範例有淺顯易懂。