Day50. 范例:十二生肖

本文同步更新於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类别图说明。)


ʕ •ᴥ•ʔ:希望这个范例有浅显易懂。


<<:  Day 20 - 天眼CNN 的耳朵和嘴巴 - RNN(1) -传统RNN

>>:  [JS] You Don't Know JavaScript [this & Object Prototypes] - Prototypes [下]

DAY14支持向量机演算法(续三)

昨天介绍完SMO算法第三步,今天就要来写这个方法第四步, 昨天我们得到aj,接下来要使用aj来更新a...

[Day 26] 实作-节庆详情页面

今天来实作节庆详情页面! 昨天有讲到我是用router-link query的方式把参数带到URL中...

[Day 10] Sass - Values

Values 有写过任何一门程序语言的应该都知道,对於每个变数的值来说都会有其对应的资料型态,而在S...

第48天-学习 crontab 工作排程

今天进度 鸟哥私房菜 - 第十五章、例行性工作排程(crontab) 我在 Crontab.guru...

Day36 ( 游戏设计 ) 钓鱼游戏

钓鱼游戏 教学原文参考:钓鱼游戏 这篇文章会介绍,如何在 Scratch 3 里使用多个角色、函式、...