iT邦幫忙

第 12 屆 iThome 鐵人賽

1

本文同步更新於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() 方法,但實作的目的不同。
日後也可能因需求調整介面,而發展出截然不同的形式。

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

最後附上類別圖:
https://ithelp.ithome.com.tw/upload/images/20201218/20111630jeKToUKOOp.png
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)


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


上一篇
Day49. 橋接模式
下一篇
Day51. 職責鏈模式
系列文
你終究都要學設計模式的,那為什麼不一開始就學呢?57
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言