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类别图说明。)

ʕ •ᴥ•ʔ:早上刚睡醒时,想到的范例。


<<:  Outlook PST Repair Tool - Repair PST data file

>>:  【图解演算法教学】Bubble Sort 的大队接力赛

Day01 - Vue3 环境设置 Vue CLI 帮我准备手术室卡关笔记

这次主要跟着三位大神学习 Vue3 重新认识 Vue.js | Kuro Hsu Vue3.0学习教...

[CSS] Flex/Grid Layout Modules, part 11

现在终於可以开始讲 Grid 单元的事情了,虽然可以讲的事情可能不多,绝大部分会围绕在造成容器影响的...

Day21【Dev】物件类型:mutable 与 Immutable

mutable 与 Immutable 比较 Immutable object 不可变物件 物件被创...

Day-05 JavaScript阵列

阵列可以一次宣告大量的变数,有节省时间、空间的优点。在JavaScript里,阵列可储存不同型态的值...

D5 第二周 程序基础

今天整理程序的基础知识,所有的程序都是由这些基础建构起来的。 我觉得程序跑起来真正重要的观念大概有三...