本文同步更新於blog
情境:这是一间国际婚礼公司
<?php
namespace App\VisitorPattern\Wedding;
class Program
{
/**
* @param string $weddingType
* @return string
*/
public function getWedding($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
'新郎:中式囍袍
新郎:黑色秀禾鞋
新娘:龙凤褂
新娘:红色秀禾鞋
';
break;
case 'Japanese':
echo
'新郎:绣有家纹的和服
新郎:雪駄
新娘:纯洁的白无垢
新娘:草履
';
break;
}
}
}
熟稔设计模式的我们,一眼就看出来改写的方向。
让我们抽出新郎与新娘!
需求一:抽出新郎 (BrideGroom) 与新娘 (Bride) 类别
<?php
namespace App\VisitorPattern\Wedding;
class BrideGroom
{
/**
* @param string $weddingType
*/
public function getClothes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新郎:中式囍袍\n";
break;
case 'Japanese':
echo
"新郎:绣有家纹的和服\n";
break;
}
}
/**
* @param string $weddingType
*/
public function getShoes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新郎:黑色秀禾鞋\n";
break;
case 'Japanese':
echo
"新郎:雪駄\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
class Bride
{
/**
* @param string $weddingType
*/
public function getClothes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新娘:龙凤褂\n";
break;
case 'Japanese':
echo
"新娘:纯洁的白无垢\n";
break;
}
}
/**
* @param string $weddingType
*/
public function getShoes($weddingType)
{
switch ($weddingType) {
case 'Chinese':
echo
"新娘:红色秀禾鞋\n";
break;
case 'Japanese':
echo
"新娘:草履\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
class Program
{
/**
* @param string $weddingType
*/
public function getWedding($weddingType)
{
$brideGroom = new BrideGroom();
$bride = new Bride();
$brideGroom->getClothes($weddingType);
$brideGroom->getShoes($weddingType);
$bride->getClothes($weddingType);
$bride->getShoes($weddingType);
}
}
正当我们得意洋洋之时,老板说了一个令人震惊的需求。
Boss:「随着版图扩张,我们之後要支援印度、乌克兰等各国的婚礼服装。」
经过观察我们可以发现,不过是哪一国的婚礼,
主角皆是新郎与新娘,且都需要取得服装与鞋子。
其资料结构是稳定的。
变动的是服装与鞋子的操作。
让我们用访问者模式改写它!
需求二:配合版图的扩张,实作访问者模式
<?php
namespace App\VisitorPattern\Wedding\Contracts;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
interface WeddingRole
{
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType);
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType);
}
<?php
namespace App\VisitorPattern\Wedding\Contracts;
interface WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role);
/**
* @param WeddingRole $role
*/
public function getShoes($role);
}
WeddingRole是原本的元素类别 (Element)。
WeddingType则是原本元素类别中的操作,会成为我们的访问者类别 (Visitor)。
根据传入的元素类别 (Element),而有对应的行为。
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class BrideGroom implements WeddingRole
{
/**
* @var string
*/
public $name = 'BrideGroom';
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType)
{
$weddingType->getClothes($this);
}
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType)
{
$weddingType->getShoes($this);
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Bride implements WeddingRole
{
/**
* @var string
*/
public $name = 'Bride';
/**
* @param WeddingType $weddingType
*/
public function getClothes($weddingType)
{
$weddingType->getClothes($this);
}
/**
* @param WeddingType $weddingType
*/
public function getShoes($weddingType)
{
$weddingType->getShoes($this);
}
}
BrideGroom与Bride会由客户端将WeddingType传入(第一次分派)
之後再将自己传给WeddingType (第二次分派)。
<?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class ChineseWedding implements WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:中式囍袍\n";
break;
case 'Bride':
echo
"新娘:龙凤褂\n";
break;
}
}
/**
* @param WeddingRole $role
*/
public function getShoes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:黑色秀禾鞋\n";
break;
case 'Bride':
echo
"新娘:红色秀禾鞋\n";
break;
}
}
}
<?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class JapaneseWedding implements WeddingType
{
/**
* @param WeddingRole $role
*/
public function getClothes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:绣有家纹的和服\n";
break;
case 'Bride':
echo
"新娘:纯洁的白无垢\n";
break;
}
}
/**
* @param WeddingRole $role
*/
public function getShoes($role)
{
$roleName = $role->name;
switch ($roleName) {
case 'BrideGroom':
echo
"新郎:雪駄\n";
break;
case 'Bride':
echo
"新娘:草履\n";
break;
}
}
}
各国婚礼会根据传入婚礼角色得不同,而有不同的行为。
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use ReflectionClass;
class WeddingTypeFactory
{
/**
* @param string $weddingType
* @return WeddingType
*/
public function create($weddingType)
{
$namespace = 'App\VisitorPattern\Wedding\Type';
$className = $weddingType . 'Wedding';
$reflector = new ReflectionClass($namespace . '\\' . $className);
return $reflector->newInstance();
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Composite
{
/**
* @var WeddingRole[]
*/
protected $children = [];
/**
* @param WeddingRole $role
* @return void
*/
public function add(WeddingRole $role)
{
$this->children[$role->name] = $role;
}
/**
* @param WeddingRole $component
* @return void
*/
public function remove(WeddingRole $role)
{
unset($this->children[$role->name]);
}
/**
* @param WeddingType $weddingType
* @return void
*/
public function display(WeddingType $weddingType)
{
foreach ($this->children as $child) {
$child->getClothes($weddingType);
$child->getShoes($weddingType);
}
}
}
<?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
use App\VisitorPattern\Wedding\WeddingTypeFactory;
use App\VisitorPattern\Wedding\Composite;
use App\VisitorPattern\Wedding\BrideGroom;
use App\VisitorPattern\Wedding\Bride;
class Program
{
/**
* @var WeddingTypeFactory
*/
protected $weddingTypeFactory;
public function __construct()
{
$this->weddingTypeFactory = new WeddingTypeFactory();
}
/**
* @param string $weddingType
*/
public function getWedding($weddingType)
{
$weddingType = $this->createWeddingType($weddingType);
$composite = new Composite();
$brideGroom = new BrideGroom();
$bride = new Bride();
$composite->add($brideGroom);
$composite->add($bride);
$composite->display($weddingType);
}
/**
* @param string $weddingType
* @return WeddingType
*/
private function createWeddingType($weddingType)
{
return $this->weddingTypeFactory->create($weddingType);
}
}
[单一职责原则]
我们将 婚礼角色(资料结构) 与 婚礼类型(操作) 视作两种不同的职责。
[开放封闭原则]
新增/修改婚礼类型时,不会修改到所有的程序码。
[介面隔离原则]
婚礼角色介面:会根据客户端传入的婚礼类型,再将自己传入後,完成行为。
婚礼类型介面:会根据传入的婚礼角色,完成行为。
[依赖反转原则]
依赖於抽象的婚礼角色介面与婚礼类型介面。
最後附上类别图:
(注:若不熟悉 UML 类别图,可参考UML类别图说明。)
ʕ •ᴥ•ʔ:揉合许多模式的范例。
<<: iOS APP 开发 OC 第七天, nil 跟 NULL 一样吗?
ISMS 启动需要事先取得高阶主管授权,在取得足够的授权之後开始推广一系列的资安相关活动。 如果发生...
试题反映理论 在试题反映理论(Item Response Theory, IRT)中 能用作因素来解...
tags: OC 30 day 自动释放池的原理 存入到自动释放池中的对象,在自动释放池被销毁的时候...
Hi 大家今天要跟大家介绍 App 样板,主要是 Web 服务。 我们主要的服务都是基本上都是 LA...
DDD 学习资源 ddd-crew 里面有许多关於 DDD 各个面向的 repo,其中这个 repo...