iT邦幫忙

第 12 屆 iThome 鐵人賽

1
Software Development

你終究都要學設計模式的,那為什麼不一開始就學呢?系列 第 54

Day54. 範例:打招呼(中介者模式)

本文同步更新於blog

情境:以下是人腦的運作程式

<?php

namespace App\MediatorPattern\SayHello;

class Program
{

    /**
     * @param string $item
     * @return string
     */
    public function see($item)
    {
        switch ($item) {
            case '認識的人':
                return $this->sayHello();
                break;

            case '熟識的人':
                return $this->waveHand();
                break;
        }
    }

    /**
     * @param string $item
     * @return string
     */
    public function hear($item)
    {
        switch ($item) {
            case '喜歡的人':
                return $this->blush();
                break;

            case '討厭的人':
                return $this->pretendToLookBusy();
                break;
        }
    }

    private function sayHello()
    {
        return '[嘴巴]發出[你好]的聲音';
    }

    private function waveHand()
    {
        return '[手]做出[揮手]的動作';
    }

    private function blush()
    {
        return '[臉]開始[發紅]';
    }

    private function pretendToLookBusy()
    {
        return '[手]做出[裝忙]的動作';
    }
}

隨著行為日趨複雜,我們可能會有更多的動作。
這些動作會聯繫著不同的器官。

因為強耦合,無論是器官的增加或是行為改變,
都會大大地影響既有程式碼。

讓我們用中介者模式改造它!


需求一:定義中介者介面 (Mediator)與合作者介面 (Colleague)

  • 使用中樞神經系統作為中介者介面 (Mediator)
<?php

namespace App\MediatorPattern\SayHello\Contracts;

interface CentralNervousSystem
{
    /**
     * @param string $organName
     * @param string $message
     * @return string
     */
    public function sendMessage($organName, $message);
}

  • 使用器官作為合作者介面 (Colleague)
<?php

namespace App\MediatorPattern\SayHello\Contracts;

interface Executable
{
    /**
     * @param string $message
     * @return string
     */
    public function execute($message);
}


需求二:定義實體中介者,來改變合作者間的依賴關係

  • 實作大腦(中介者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Contracts\CentralNervousSystem;
use App\MediatorPattern\SayHello\Abstracts\Organ;

class Brain implements CentralNervousSystem
{
    /**
     * @var Organ[]
     */
    protected $organs = [];

    /**
     * @param string $organName
     * @param string $message
     * @return string
     */
    public function sendMessage($organName, $message)
    {
        $organ = $this->organs[$organName];
        return $organ->execute($message);
    }

    public function setOrgan(Organ $organ)
    {
        $organName = $organ->getName();
        $this->organs[$organName] = $organ;
    }
}

  • 抽象器官(合作者)
<?php

namespace App\MediatorPattern\SayHello\Abstracts;

use App\MediatorPattern\SayHello\Contracts\Executable;
use App\MediatorPattern\SayHello\Brain;

abstract class Organ implements Executable
{
    /**
     * @var string
     */
    protected $name = 'Unknown';

    /**
     * @var Brain
     */
    protected $brain;

    public function __construct(Brain $brain)
    {
        $this->brain = $brain;
    }


    public function getName()
    {
        return $this->name;
    }
}
  • 實作眼睛 (合作者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Abstracts\Organ;


class Eye extends Organ
{
    /**
     * @var string
     */
    protected $name = '眼睛';

    /**
     * @param string $message
     * @return string
     */
    public function execute($message)
    {
        switch ($message) {
            case '認識的人':
                return $this->brain->sendMessage('嘴巴', '你好');
                break;

            case '熟識的人':
                return $this->brain->sendMessage('手', '揮手');
                break;
        }
    }
}

  • 實作耳朵 (合作者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Abstracts\Organ;

class Ear extends Organ
{
    /**
     * @var string
     */
    protected $name = '耳朵';

    /**
     * @param string $message
     * @return string
     */
    public function execute($message)
    {
        switch ($message) {
            case '喜歡的人':
                return $this->brain->sendMessage('臉', '發紅');
                break;

            case '討厭的人':
                return $this->brain->sendMessage('手', '裝忙');
                break;
        }
    }
}

  • 實作手 (合作者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Abstracts\Organ;

class Hand extends Organ
{
    protected $name = '手';

    /**
     * @param string $message
     * @return string
     */
    public function execute($message)
    {
        return "[手]做出[$message]的動作";
    }
}

  • 實作嘴巴 (合作者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Abstracts\Organ;

class Mouth extends Organ
{
    /**
     * @var string
     */
    protected $name = '嘴巴';

    /**
     * @param string $message
     * @return string
     */
    public function execute($message)
    {
        return "[嘴巴]發出[$message]的聲音";
    }
}

  • 實作臉 (合作者)
<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Abstracts\Organ;

class Face extends Organ
{
    /**
     * @var string
     */
    protected $name = '臉';

    /**
     * @param string $message
     * @return string
     */
    public function execute($message)
    {
        return "[臉]開始[$message]";
    }
}

以這個範例來說,中介者是大腦,合作者則是各個器官。

當A器官要呼叫B器官,執行某些動作時,
會透過大腦,使得A器官不必知道真正的B器官是誰(鬆耦合)。


需求三:改寫既有程式碼

<?php

namespace App\MediatorPattern\SayHello;

use App\MediatorPattern\SayHello\Brain;
use App\MediatorPattern\SayHello\Eye;
use App\MediatorPattern\SayHello\Mouth;
use App\MediatorPattern\SayHello\Hand;
use App\MediatorPattern\SayHello\Ear;
use App\MediatorPattern\SayHello\Face;

class Program
{
    /**
     * @var Brain
     */
    protected $brain;

    /**
     * @var Eye
     */
    protected $eye;

    /**
     * @var Mouth
     */
    protected $mouth;

    /**
     * @var Hand
     */
    protected $hand;

    /**
     * @var Ear
     */
    protected $ear;

    /**
     * @var Face
     */
    protected $face;

    public function __construct()
    {
        $this->brain = $this->resolveBrainAndOrgans();
    }

    /**
     * @param string $item
     * @return string
     */
    public function see($item)
    {
        return $this->eye->execute($item);
    }

    /**
     * @param string $item
     * @return string
     */
    public function hear($item)
    {
        return $this->ear->execute($item);
    }

    private function resolveBrainAndOrgans()
    {
        $this->brain = new Brain();
        $this->resolveOrgans();

        $this->brain->setOrgan($this->eye);
        $this->brain->setOrgan($this->mouth);
        $this->brain->setOrgan($this->hand);
        $this->brain->setOrgan($this->ear);
        $this->brain->setOrgan($this->face);
    }

    private function resolveOrgans()
    {
        $this->eye = new Eye($this->brain);
        $this->mouth = new Mouth($this->brain);
        $this->hand = new Hand($this->brain);
        $this->ear = new Ear($this->brain);
        $this->face = new Face($this->brain);
    }
}

[單一職責原則]
我們將器官的功能器官間的關係視作兩種不同的職責。

藉由大腦(中介者)負責聯絡各個器官(合作者)執行對應的行為。

[開放封閉原則]
無論是新增/修改器官,或者新增/修改器官間的關係,
我們都不會改動到所有程式碼。

[介面隔離原則]
中介者介面:負責傳送器官間的訊息。
合作者介面:負責執行該器官的功能。

[依賴反轉原則]
大腦依賴於合作者介面。
器官負責實作合作者介面。

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

ʕ •ᴥ•ʔ:早上剛睡醒時,想到的範例。


上一篇
Day53. 中介者模式
下一篇
Day55. 訪問者模式
系列文
你終究都要學設計模式的,那為什麼不一開始就學呢?57

尚未有邦友留言

立即登入留言