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 35 36 37
| <?php
namespace App\BuilderPattern\Vacation;
class Program {
public function getDomesticTravel() {
return [ 'from' => 'Kaohsiung', 'to' => 'Taipei', 'day' => 1, 'transport' => 'High Speed Rail' ]; }
public function getInternationalTravel() {
return [ 'from' => 'Kaohsiung', 'to' => 'Tokyo', 'day' => 5, 'transport' => 'Airplane', 'hotel' => 'Disney Hotel' ]; } }
|
老闆希望我們能提供更簡便的方式,來規劃不同的旅遊行程。
讓我們用建造者模式改造它。
需求一:實作旅遊行程 (產品類別)
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?php
namespace App\BuilderPattern\Vacation;
class Itinerary {
protected $from;
protected $to;
protected $day;
protected $hotel;
protected $transport;
public function __set($name, $value) { $this->$name = $value; }
public function __get($name) { return $this->$name; }
public function toArray() { $result = get_object_vars($this);
foreach ($result as $name => $value) { if (is_null($value)) { unset($result[$name]); } }
return $result; } }
|
主要都是getter與setter方法。
當行程規劃好時,我們會透過toArray() 方法來輸出。
需求二:實作行程建造者 (建造者類別)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
namespace App\BuilderPattern\Vacation\Contracts;
use App\BuilderPattern\Vacation\Itinerary;
interface ItineraryPlanable { public function from(string $from): self;
public function to(string $to): self;
public function spendDays(int $day): self;
public function stayAt(string $hotel): self;
public function travelBy(string $transport): self;
public function getItinerary(): Itinerary; }
|
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| <?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\Itinerary; use App\BuilderPattern\Vacation\Contracts\ItineraryPlanable;
class ItineraryBuilder implements ItineraryPlanable {
protected $itinerary;
public function __construct() { $this->itinerary = new Itinerary(); }
public function from(string $from): self { $this->itinerary->from = $from; return $this; }
public function to(string $to): self { $this->itinerary->to = $to; return $this; }
public function spendDays(int $day): self { $this->itinerary->day = $day; return $this; }
public function stayAt(string $hotel): self { $this->itinerary->hotel = $hotel; return $this; }
public function travelBy(string $transport): self { $this->itinerary->transport = $transport; return $this; }
public function getItinerary(): Itinerary { return $this->itinerary; } }
|
行程建造者用了流式接口 (Fluent 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 38 39 40 41 42 43 44 45 46 47 48 49
| <?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\Contracts\ItineraryPlanable;
class TravelAgency {
protected $itineraryBuilder;
public function __construct(ItineraryPlanable $itineraryBuilder) { $this->itineraryBuilder = $itineraryBuilder; }
public function getHighSpeedRailItinerary() { $itinerary = $this->itineraryBuilder ->from('Kaohsiung') ->to('Taipei') ->travelBy('High Speed Rail') ->spendDays(1) ->getItinerary();
return $itinerary->toArray(); }
public function getFiveDaysTokyoItinerary() { $itinerary = $this->itineraryBuilder ->from('Kaohsiung') ->to('Tokyo') ->travelBy('Airplane') ->spendDays(5) ->stayAt('Disney Hotel') ->getItinerary();
return $itinerary->toArray(); } }
|
透過旅行社 (指揮者類別),我們封裝了行程的實作。
使得客戶端不用知道行程的建造過程。
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
| <?php
namespace App\BuilderPattern\Vacation;
use App\BuilderPattern\Vacation\TravelAgency; use App\BuilderPattern\Vacation\ItineraryBuilder;
class Program {
public function getDomesticTravel() { $itineraryBuilder = new ItineraryBuilder(); $travelAgency = new TravelAgency($itineraryBuilder); return $travelAgency->getHighSpeedRailItinerary(); }
public function getInternationalTravel() { $itineraryBuilder = new ItineraryBuilder(); $travelAgency = new TravelAgency($itineraryBuilder); return $travelAgency->getFiveDaysTokyoItinerary(); } }
|
[單一職責原則]
我們將指揮者類別、建造者類別與產品類別,視為三種不同的職責。
由旅行社指揮行程建造者來構建行程。
[開放封閉原則]
當新增/修改行程時,我們只要調整指揮者類別。
當新增/修改行程內部的邏輯時,我們僅需修改產品類別。
[依賴反轉原則]
指揮者類別依賴於抽象的建造者介面。
建造者類別實作抽象的建造者介面。
ʕ •ᴥ•ʔ:核心精神在於分離建造過程與產品本身的邏輯。