Pattern: 訪問者模式
Class Diagram: 各國婚禮
情境:這是一間國際婚禮公司
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace App\VisitorPattern\Wedding;
class Program {
public function getWedding($weddingType) { switch ($weddingType) { case 'Chinese': echo '新郎:中式囍袍 新郎:黑色秀禾鞋 新娘:龍鳳褂 新娘:紅色秀禾鞋 '; break;
case 'Japanese': echo '新郎:繡有家紋的和服 新郎:雪駄 新娘:純潔的白無垢 新娘:草履 '; break; } } }
|
熟稔設計模式的我們,一眼就看出來改寫的方向。
讓我們抽出新郎與新娘!
需求一:抽出新郎 (BrideGroom) 與新娘 (Bride) 類別
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?php
namespace App\VisitorPattern\Wedding;
class BrideGroom {
public function getClothes($weddingType) { switch ($weddingType) { case 'Chinese': echo "新郎:中式囍袍\n"; break;
case 'Japanese': echo "新郎:繡有家紋的和服\n"; break; } }
public function getShoes($weddingType) { switch ($weddingType) { case 'Chinese': echo "新郎:黑色秀禾鞋\n"; break;
case 'Japanese': echo "新郎:雪駄\n"; break; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?php
namespace App\VisitorPattern\Wedding;
class Bride {
public function getClothes($weddingType) { switch ($weddingType) { case 'Chinese': echo "新娘:龍鳳褂\n"; break;
case 'Japanese': echo "新娘:純潔的白無垢\n"; break; } }
public function getShoes($weddingType) { switch ($weddingType) { case 'Chinese': echo "新娘:紅色秀禾鞋\n"; break;
case 'Japanese': echo "新娘:草履\n"; break; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\VisitorPattern\Wedding;
class Program {
public function getWedding($weddingType) { $brideGroom = new BrideGroom(); $bride = new Bride();
$brideGroom->getClothes($weddingType); $brideGroom->getShoes($weddingType);
$bride->getClothes($weddingType); $bride->getShoes($weddingType); } }
|
正當我們得意洋洋之時,老闆說了一個令人震驚的需求。
Boss:「隨著版圖擴張,我們之後要支援印度、烏克蘭等各國的婚禮服裝。」
經過觀察我們可以發現,不過是哪一國的婚禮,
主角皆是新郎與新娘,且都需要取得服裝與鞋子。
其資料結構是穩定的。
變動的是服裝與鞋子的操作。
讓我們用訪問者模式改寫它!
需求二:配合版圖的擴張,實作訪問者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace App\VisitorPattern\Wedding\Contracts;
use App\VisitorPattern\Wedding\Contracts\WeddingType;
interface WeddingRole {
public function getClothes($weddingType);
public function getShoes($weddingType); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
namespace App\VisitorPattern\Wedding\Contracts;
interface WeddingType {
public function getClothes($role);
public function getShoes($role); }
|
WeddingRole 是原本的 元素類別 (Element) 。
WeddingType 則是原本元素類別中的操作,會成為我們的 訪問者類別 (Visitor) 。
根據傳入的 元素類別 (Element) ,而有對應的行為。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole; use App\VisitorPattern\Wedding\Contracts\WeddingType;
class BrideGroom implements WeddingRole {
public $name = 'BrideGroom';
public function getClothes($weddingType) { $weddingType->getClothes($this); }
public function getShoes($weddingType) { $weddingType->getShoes($this); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole; use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Bride implements WeddingRole {
public $name = 'Bride';
public function getClothes($weddingType) { $weddingType->getClothes($this); }
public function getShoes($weddingType) { $weddingType->getShoes($this); } }
|
BrideGroom 與 Bride 會由客戶端將 WeddingType 傳入(第一次分派)
之後再將自己傳給 WeddingType (第二次分派)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType; use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class ChineseWedding implements WeddingType {
public function getClothes($role) { $roleName = $role->name;
switch ($roleName) { case 'BrideGroom': echo "新郎:中式囍袍\n"; break;
case 'Bride': echo "新娘:龍鳳褂\n"; break; } }
public function getShoes($role) { $roleName = $role->name;
switch ($roleName) { case 'BrideGroom': echo "新郎:黑色秀禾鞋\n"; break;
case 'Bride': echo "新娘:紅色秀禾鞋\n"; break; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <?php
namespace App\VisitorPattern\Wedding\Type;
use App\VisitorPattern\Wedding\Contracts\WeddingType; use App\VisitorPattern\Wedding\Contracts\WeddingRole;
class JapaneseWedding implements WeddingType {
public function getClothes($role) { $roleName = $role->name;
switch ($roleName) { case 'BrideGroom': echo "新郎:繡有家紋的和服\n"; break;
case 'Bride': echo "新娘:純潔的白無垢\n"; break; } }
public function getShoes($role) { $roleName = $role->name;
switch ($roleName) { case 'BrideGroom': echo "新郎:雪駄\n"; break;
case 'Bride': echo "新娘:草履\n"; break; } } }
|
各國婚禮會根據傳入婚禮角色得不同,而有不同的行為。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingType; use ReflectionClass;
class WeddingTypeFactory {
public function create($weddingType) { $namespace = 'App\VisitorPattern\Wedding\Type'; $className = $weddingType . 'Wedding';
$reflector = new ReflectionClass($namespace . '\\' . $className); return $reflector->newInstance(); } }
|
- 實作物件結構類別,用來放入元素,便於我們實現遍歷。方便客戶端的呼叫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\VisitorPattern\Wedding;
use App\VisitorPattern\Wedding\Contracts\WeddingRole; use App\VisitorPattern\Wedding\Contracts\WeddingType;
class Composite {
protected $children = [];
public function add(WeddingRole $role) { $this->children[$role->name] = $role; }
public function remove(WeddingRole $role) { unset($this->children[$role->name]); }
public function display(WeddingType $weddingType) { foreach ($this->children as $child) { $child->getClothes($weddingType); $child->getShoes($weddingType); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?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 {
protected $weddingTypeFactory;
public function __construct() { $this->weddingTypeFactory = new WeddingTypeFactory(); }
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); }
private function createWeddingType($weddingType) { return $this->weddingTypeFactory->create($weddingType); } }
|
[單一職責原則]
我們將婚禮角色(資料結構)與婚禮類型(操作)視作兩種不同的職責。
[開放封閉原則]
新增/修改婚禮類型時,不會修改到所有的程式碼。
[介面隔離原則]
婚禮角色介面:會根據客戶端傳入的婚禮類型,再將自己傳入後,完成行為。
婚禮類型介面:會根據傳入的婚禮角色,完成行為。
[依賴反轉原則]
依賴於抽象的婚禮角色介面與婚禮類型介面。
ʕ •ᴥ•ʔ:揉合許多模式的範例。