Larvel使用策略模式 – 範例 : 訊息寄送執行器
前言:
今天想來記錄一下近期在Laravel專案中用到策略模式的實際演練,這樣的方式無非是解耦每個class所負責的事情,以及可以更加彈性的設計每個class的內容
外部參考資訊 : https://www.runoob.com/design-pattern/strategy-pattern.html
範例:
今天的範例是我們需要設計出訊息傳送的中央平台,可以將資料傳遞到Email、SMS、Slack等三個平台,也需要預留未來擴充其他平台的可能性
過往的寫法:
以往我們可以會設計三個ServiceClass,分別用於寄送三個平台的訊息例如: EmailService、SMSService、SlackService,然後在Controller中呼叫他像這個樣子
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 |
class EmailService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送Email */ .... } } class SMSService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送SMS */ .... } } class SlackService { public function Send(string $receiver, string $content){ .... /* 實際實行寄送Slack */ .... } } |
然後在Controller中呼叫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class MessageController extend Controller { public function index(Request $request){ if($request->get('platform) === 'email'){ new EmailService()->send(...$request->all()); } if($request->get('platform) === 'SMS'){ new SMSService()->send(...$request->all()); } if($request->get('platform) === 'Slack'){ new SlackService()->send(...$request->all()); } } } |
當然,我們也可以調整成上一篇提到的動態呼叫的寫法,讓原始碼更簡潔,像是這個樣子
1 2 3 4 5 6 |
class MessageController extend Controller { public function index(Request $request){ $platform = $request->get('platform').'Service'; new $platform(...$request->all()); } } |
只是在彈性上與邏輯的完整度上都比較缺乏,今天就記錄一下策略模式的使用方式
策略模式範例 :
實際上這個範例使用了策略模式+工廠模式來完整處理資料流
定義策略
跟先前相同,我們需要定義三個PlatformClass,分別處理不同平台的資料,並且都需要實作同一個Interface
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 |
Interface PlatformContract { public function send(); } class EmailPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } class SMSPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } class SlackPlatform implements PlatformContract { public function __construct(public string $receiver, public string $content){ } public function send(){ //實際執行處 } } |
下一步我們要定義一個執行器,概念上是三個Platform的上層邏輯,負責處理整個寄送資訊的資料流,也是從Controller呼叫的入口
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 |
class notificationService { protected array $payloadData; protected PlatformContract $platform; protected Manager $manager; public function __construct( $this->manager = new Manager(); ){} public function trigger(array $payload){ $this->_set_payload(payload)->_prepare()->_send(); } protected function _set_payload(array $payload){ $this->payloadData = $payload; return $this; } protected function _prepare(){ $this->platform = $this->manager($this->payloadData['platform'],$this->payloadData)->create(); return $this; } protected function _send(){ return $this->platform->send(); } } |
簡單說明一下內容
Manager的部分後續說明,他是負責把platform new出來的class
Trigger是Notification的入口,只要將需要的參數傳入,後續會執行
1._set_payload,儲存參數
2._prepare,生成平台
3._send,呼叫平台的send()實際將資料送出
我們要說明的重點在_prepare身上,
我們不在_prepare身上直接new platform,而是透過Manager來處理,Manager就是類似工廠的概念,負責產出需要的物件
Manager大概可以長這個樣子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Manager { protected string $platform; protected array $payload; public function __construct(string $platform_name, array $payload){ $this->platform = $platform_name."Platform"; $this->payload = $payload; } public function create(){ $__NAMESPACE__ = "App\\Service\\"; $target = $__NAMESPACE__.$this->platform; return new $target($this->payload); } } |
大概就是透過create() function將需要的Object物件new好回傳,這樣子可以更解耦每個class負責的事情
那麼我們就可以在Controller中這樣子呼叫
1 2 3 4 5 6 |
class MessageController extend Controller { public function index(Request $request){ new NotificationService()->trigger(...$request->all()); } } |
當然如果要再更嚴謹一些的話,就可以把所有payload都改成透過Dto 物件來傳遞
總結以上:
- 透過NotificationService作為服務入口
- 在Notification中透過Manager將需要的平台Object new出來
- 統一透過_send function來呼叫每個平台的Object的send() function來實際將資料送出
以上這樣子設計,如果需要多一個平台,只要多設計一個XXXXPlatform就可以了喔