簡介
設計模式用於解決反覆出現的問題,是解決特定問題的指導方針。設計模式不是在應用中引用的類、package 或者庫,而是在某些特定場景下解決特定問題的指導方針。
設計模式用於解決反覆出現的問題,是解決某些特定問題的指導方針。
維基百科中這樣描述設計模式:
在軟體工程中,設計模式是針對軟體設計中普遍存在(反覆出現)的各種問題,所提出的可複用型解決方案。設計模式並不直接完成程式碼的編寫,而是描述在不同情況下如何解決問題。
注意
- 設計模式並非解決所有問題的銀彈。
- 不要強制使用設計模式,否則結果可能適得其反。謹記:設計模式是用來解決問題的,而不是來尋找問題的,不要過度思考。
- 如果在對的地方對的時機使用設計模式,它會是你的救世主。反之,將會一團糟。
另注:下面的示例程式碼是用 PHP7 實現的,因為概念是一樣的,所以語言並不會阻礙你理解設計模式。其他語言版本的實現正在進行中。
設計模式分類
- 建立型模式
- 結構型模式
- 行為型模式
建立型模式
概述
建立型模式專注於如何初始化物件 。
維基百科
在軟體工程中,建立型模式是處理物件建立的設計模式,試圖根據實際情況使用合適的方式建立物件。基本的物件建立方式可能會導致設計上的問題,或增加設計的複雜度。建立型模式通過以某種方式控制物件的建立來解決這些問題。
分類
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
- 生成器模式
- 原型模式
- 單例模式
? 簡單工廠模式
現實生活示例
想象一下,你正在建造一座房子而且需要幾扇房門,如果每次需要房門的時候,不是用工廠製造的房門,而是穿上木匠服,然後開始自己製造房門,將會搞得一團糟。
概述
簡單工廠模式只是為客戶端建立例項,而不將任何例項化邏輯暴露給客戶端。
維基百科
在物件導向程式設計中,工廠通常是一個用來建立其他物件的物件。通常來講,工廠是指某個功能或方法,此功能或方法返回不同型別的物件或者類的某個方法呼叫,返回的東西看起來是「新的」。
程式示例
首先是房門
的介面和實現
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 |
interface Door { public function getWidth(): float; public function getHeight(): float; } class WoodenDoor implements Door { protected $width; protected $height; public function __construct(float $width, float $height) { $this->width = $width; $this->height = $height; } public function getWidth(): float { return $this->width; } public function getHeight(): float { return $this->height; } } |
然後是生產房門的工廠
1 2 3 4 5 6 7 |
class DoorFactory { public static function makeDoor($width, $height): Door { return new WoodenDoor($width, $height); } } |
這樣使用
1 2 3 |
$door = DoorFactory::makeDoor(100, 200); echo 'Width: ' . $door->getWidth(); echo 'Height: ' . $door->getHeight(); |
何時使用?
如果建立物件不僅僅是一些變數的初始化,還涉及某些邏輯,那麼將其封裝到一個專用工廠中取代隨處使用的重複程式碼是有意義的。
? 工廠方法模式
現實生活示例
考慮招聘經理的情況。一個人不可能應付所有職位的面試,對於空缺職位,招聘經理必須委派不同的人去面試。
概述
工廠方法模式提供了一種將例項化邏輯委託給子類的方法。
維基百科
在基於類的程式設計中,工廠方法模式是一種使用了工廠方法的建立型設計模式,在不指定物件具體型別的情況下,處理建立物件的問題。建立物件不是通過呼叫構造器而是通過呼叫工廠方法(在介面中指定工廠方法並在子類中實現或者在基類中實現,隨意在派生類中重寫)來完成。
程式示例
以上述招聘經理為例,首先給出一個面試官介面及實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
interface Interviewer { public function askQuestions(); } class Developer implements Interviewer { public function askQuestions() { echo 'Asking about design patterns!'; } } class CommunityExecutive implements Interviewer { public function askQuestions() { echo 'Asking about community building'; } } |
然後建立 HiringManager
1 2 3 4 5 6 7 8 9 10 11 12 |
abstract class HiringManager { // Factory method abstract public function makeInterviewer(): Interviewer; public function takeInterview() { $interviewer = $this->makeInterviewer(); $interviewer->askQuestions(); } } |
現在,任何子類都可以繼承 HiringManager
並委派相應的面試官
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class DevelopmentManager extends HiringManager { public function makeInterviewer(): Interviewer { return new Developer(); } } class MarketingManager extends HiringManager { public function makeInterviewer(): Interviewer { return new CommunityExecutive(); } } |
這樣使用
1 2 3 4 5 |
$devManager = new DevelopmentManager(); $devManager->takeInterview(); // Output: Asking about design patterns $marketingManager = new MarketingManager(); $marketingManager->takeInterview(); // Output: Asking about community building. |
何時使用?
類中的一些常見處理需要在執行時動態決定所需的子類,換句話說,當客戶端不知道可能需要的確切子類時,使用工廠方法模式。
? 抽象工廠模式
現實生活示例
擴充套件一下簡單工廠模式
中的房門例子。基於所需,你可能需要從木門店獲取木門,從鐵門店獲取鐵門或者從相關的門店獲取 PVC 門。進一步講,你可能需要不同種類的專家來安裝房門,比如木匠安裝木門,焊接工安裝鐵門等等。正如你所料,房門有了依賴,木門需要木匠,鐵門需要焊接工。
概述
一組工廠的工廠:將相關或者互相依賴的單個工廠聚集在一起,而不指定這些工廠的具體類。
維基百科
抽象工廠模式提供了一種方式,這種方式可以封裝一組具有共同主題的個體工廠,而不指定這些工廠的具體類。
程式設計示例
以上述房門為例,首先給出 Door
介面和一些實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
interface Door { public function getDescription(); } class WoodenDoor implements Door { public function getDescription() { echo 'I am a wooden door'; } } class IronDoor implements Door { public function getDescription() { echo 'I am an iron door'; } } |
然後根據每種房門型別給出對應的安裝專家
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
interface DoorFittingExpert { public function getDescription(); } class Welder implements DoorFittingExpert { public function getDescription() { echo 'I can only fit iron doors'; } } class Carpenter implements DoorFittingExpert { public function getDescription() { echo 'I can only fit wooden doors'; } } |
現在抽象工廠可以將相關的物件組建在一起,也就是說,木門工廠會生成木門並提供木門安裝專家,鐵門工廠會生產鐵門並提供鐵門安裝專家。
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 |
interface DoorFactory { public function makeDoor(): Door; public function makeFittingExpert(): DoorFittingExpert; } // Wooden factory to return carpenter and wooden door class WoodenDoorFactory implements DoorFactory { public function makeDoor(): Door { return new WoodenDoor(); } public function makeFittingExpert(): DoorFittingExpert { return new Carpenter(); } } // Iron door factory to get iron door and the relevant fitting expert class IronDoorFactory implements DoorFactory { public function makeDoor(): Door { return new IronDoor(); } public function makeFittingExpert(): DoorFittingExpert { return new Welder(); } } |
這樣使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$woodenFactory = new WoodenDoorFactory(); $door = $woodenFactory->makeDoor(); $expert = $woodenFactory->makeFittingExpert(); $door->getDescription(); // Output: I am a wooden door $expert->getDescription(); // Output: I can only fit wooden doors // Same for Iron Factory $ironFactory = new IronDoorFactory(); $door = $ironFactory->makeDoor(); $expert = $ironFactory->makeFittingExpert(); $door->getDescription(); // Output: I am an iron door $expert->getDescription(); // Output: I can only fit iron doors |
正如你看到的,木門工廠將木匠
和木門
封裝在一起,同樣地,鐵門工廠將鐵門
和焊接工
封裝在一起。這樣就可以幫助我們確保,對於每一扇生產出來的門,都能搭配正確的安裝工。
何時使用?
當存在相關的依賴並涉及到稍複雜的建立邏輯時,使用抽象工廠模式。
? 生成器模式
現實生活示例
想象一下你在 Hardee’s 餐廳點了某個套餐,比如「大 Hardee 套餐」,然後工作人員會正常出餐,這是簡單工廠模式。但是在很多情況下,建立邏輯可能涉及到更多步驟。比如,你想要一個定製的 Subway
套餐,對於你的漢堡如何製作有幾個選項可供選擇,比如你想要什麼型別的醬汁?你想要什麼乳酪? 在這種情況下,建造者模式便可以派上用場。
概述
允許建立不同風格的物件,同時避免構造器汙染。當建立多種風格的物件時或者建立物件時涉及很多步驟,可以使用生成器模式。
維基百科
生成器模式是一種物件建立軟體設計模式,其目的是找到重疊構造器反面模式的解決方案。
既然提到了,那我就補充一下什麼是重疊構造器反面模式
。 我們時不時地會看到如下建構函式:
1 2 3 |
public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true) { } |
正如你看到的,構造器引數的數量可能會迅速失控,並且引數的排列可能讓人難以理解。 如果將來要新增更多選項,此引數列表可能會不斷增長,這被稱為重疊構造器反面模式
。
程式示例
理想之選是使用生成器模式,首先給出漢堡類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Burger { protected $size; protected $cheese = false; protected $pepperoni = false; protected $lettuce = false; protected $tomato = false; public function __construct(BurgerBuilder $builder) { $this->size = $builder->size; $this->cheese = $builder->cheese; $this->pepperoni = $builder->pepperoni; $this->lettuce = $builder->lettuce; $this->tomato = $builder->tomato; } } |
然後是 builder
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 |
class BurgerBuilder { public $size; public $cheese = false; public $pepperoni = false; public $lettuce = false; public $tomato = false; public function __construct(int $size) { $this->size = $size; } public function addPepperoni() { $this->pepperoni = true; return $this; } public function addLettuce() { $this->lettuce = true; return $this; } public function addCheese() { $this->cheese = true; return $this; } public function addTomato() { $this->tomato = true; return $this; } public function build(): Burger { return new Burger($this); } } |
這樣使用
1 2 3 4 5 |
$burger = (new BurgerBuilder(14)) ->addPepperoni() ->addLettuce() ->addTomato() ->build(); |
何時使用?
當需要構建不同風格的物件,同時需要避免構造器重疊時使用生成器模式。與工廠模式的主要區別在於:當建立過程一步到位時,使用工廠模式,而當建立過程需要多個步驟時,使用生成器模式。
? 原型模式
現實生活示例
還記得多莉嗎?那隻克隆羊。這裡不深入細節,關鍵點在於克隆。
概述
基於現有物件通過克隆建立物件。
維基百科
在軟體開發過程中,原型模式是一種建立型設計模式。當要建立的物件型別由原型例項確定時,將通過克隆原型例項生成新物件。
簡言之,原型模式允許你建立現有物件的副本並根據需要進行修改,而不是從頭開始建立物件並進行設定。
程式設計示例
使用 PHP 的 clone
方法可以輕鬆實現
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 |
class Sheep { protected $name; protected $category; public function __construct(string $name, string $category = 'Mountain Sheep') { $this->name = $name; $this->category = $category; } public function setName(string $name) { $this->name = $name; } public function getName() { return $this->name; } public function setCategory(string $category) { $this->category = $category; } public function getCategory() { return $this->category; } } |
可以像下面這樣克隆
1 2 3 4 5 6 7 8 9 |
$original = new Sheep('Jolly'); echo $original->getName(); // Jolly echo $original->getCategory(); // Mountain Sheep // Clone and modify what is required $cloned = clone $original; $cloned->setName('Dolly'); echo $cloned->getName(); // Dolly echo $cloned->getCategory(); // Mountain sheep |
此外,你可以使用魔術方法 **clone
來修改克隆行為。
何時使用
當需要建立一個與已有物件類似的物件,或者當建立物件的成本比克隆更高時,使用原型模式。
? 單例模式
現實生活示例
一個國家同一時間只能有一位總統。只要使命召喚,這個總統就必須採取行動。 這裡的總統就是一個單例。
概述
確保特定類的物件只被建立一次。
維基百科
在軟體工程中,單例模式是一種軟體設計模式,用來限制類初始化為物件。當恰恰只需要一個物件來協調整個系統的功能時,單例模式非常有用。
實際上,單例模式被認為是反模式,應該避免過度使用。 單例模式並非不好,可能有時候很有用,但應謹慎使用,因為它在你的應用程式中引入了全域性狀態,一處更改可能會影響其他地方,並且可能會變得很難除錯。 另外不好的一點是單例模式會使程式碼緊耦合,單例也很難mock。
程式設計示例
要建立一個單例,需要將建構函式設為 private
,禁用克隆,禁用副檔名,並建立靜態變數來容納例項
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 |
final class President { private static $instance; private function __construct() { // Hide the constructor } public static function getInstance(): President { if (!self::$instance) { self::$instance = new self(); } return self::$instance; } private function __clone() { // Disable cloning } private function __wakeup() { // Disable unserialize } } |
這樣使用
1 2 3 4 |
$president1 = President::getInstance(); $president2 = President::getInstance(); var_dump($president1 === $president2); // true |
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!