
?? 對設計模式的極簡說明!??
這個話題可以輕易讓任何人糊涂。現在我嘗試通過用<i>最簡單</i>的方式說明它們,來讓你(和我)把他們吃透。
?? 簡介
設計模式用來解決重復的問題;是解決特定問題的指導方針。它們不是類(class),包(packages),或者庫(libraries),你不能引入它們,然后等待奇跡發生。它們是針對解決特定環境下特定問題的指導方針。
設計模式用來解決重復的問題;是解決特定問題的指導方針
維基百科的解釋
In software engineering, a software design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations.
?? 請注意
- 設計模式不是解決你所有問題的銀彈。
- 不要嘗試強行使用它們;如果做了,不好的事情可能發生。請記住設計模式是解決問題的方案,不是發現問題;所以不要過度思考。
- 如果在正確的地方以正確的方式使用,它們被證明是有幫助的;否則結果可能是一堆可怕混亂的代碼。
下面的代碼示例使用 PHP-7 書寫,但你不應止步于此,因為理念是相通的。再加上,s對其他語言的支持正在路上。
設計模式的種類
創建型模式
白話
創建型模式側重如何實例化一個對象或一組相關對象。
維基百科
In software engineering, creational design patterns are design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation.
- 簡單工廠模式 Simple Factory
- 工廠方法模式 Factory Method
- 抽象工廠模式 Abstract Factory
- 建造者模式 Builder
- 原型模式 Prototype
- 單例模式 Singleton
?? 簡單工廠模式
現實例子
假設,你正在建造一所房子,你需要門。如果每次你需要一扇門你都要穿上木工服開始在房子里造扇門,將會是一團亂。取而代之的是讓工廠造好。
白話
簡單工廠模式在不暴露生成邏輯的前提下生成一個實例。
維基百科
In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new".
代碼例子
首先,我們有一個門的接口和實現
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;
}
}
然后,我們有了工廠來制造和返回門
class DoorFactory {
public static function makeDoor($width, $height) : Door {
return new WoodenDoor($width, $height);
}
}
然后這樣使用
$door = DoorFactory::makeDoor(100, 200);
echo 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();
什么時候使用?
當創建一個對象不只是幾個賦值和邏輯計算,把這件工作交給一個工廠而不是到處重復相同的代碼就比較合適了。
?? 工廠方法模式
現實例子
設想一個人事經理。一個人是不可能面試所有職位的。基于職位空缺,她必須把面試委托給不同的人。
白話
它提供了一個把生成邏輯移交給子類的方法。
維基百科
In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.
代碼例子
以上面的人事經理為例。首先我們有一個面試官接口和一些實現
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
abstract class HiringManager {
// Factory method
abstract public function makeInterviewer() : Interviewer;
public function takeInterview() {
$interviewer = $this->makeInterviewer();
$interviewer->askQuestions();
}
}
現在任何一個都可以繼承它,并且生成需要的面試官
class DevelopmentManager extends HiringManager {
public function makeInterviewer() : Interviewer {
return new Developer();
}
}
class MarketingManager extends HiringManager {
public function makeInterviewer() : Interviewer {
return new CommunityExecutive();
}
}
然后可以這樣使用
$devManager = new DevelopmentManager();
$devManager->takeInterview(); // Output: Asking about design patterns
$marketingManager = new MarketingManager();
$marketingManager->takeInterview(); // Output: Asking about community building.
何時使用?
當一個類里有普遍性的處理過程,但是子類要在運行時才確定。或者換句話說,調用者不知道它需要哪個子類。
?? 抽象工廠模式
現實例子
擴展我們簡單工廠模式的例子。基于你的需求,你可以從木門店得到一扇木門,從鐵門店得到一扇鐵門,或者從塑料門店得到一扇塑料門。而且你需要一個有不同專長的人來安裝這扇門,比如一個木匠來安木門,焊工來安鐵門等。正如你看的,門和安裝工有依賴性,木門需要木匠,鐵門需要焊工等。
白話
一個制造工廠的工廠;一個工廠把獨立但是相關/有依賴性的工廠進行分類,但是不需要給出具體的類。
維基百科
The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes
代碼例子
翻譯上面門的例子。首先我們有了門 Door
的接口和一些實現
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';
}
}
然后我們有了每種門的安裝專家
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';
}
}
現在我們有了抽象工廠來創建全部相關的對象,即木門工廠制造木門和木門安裝專家,鐵門工廠制造鐵門和鐵門安裝專家
interface DoorFactory {
public function makeDoor() : Door;
public function makeFittingExpert() : DoorFittingExpert;
}
// 木頭工廠返回木門和木匠
class WoodenDoorFactory implements DoorFactory {
public function makeDoor() : Door {
return new WoodenDoor();
}
public function makeFittingExpert() : DoorFittingExpert{
return new Carpenter();
}
}
// 鐵門工廠返回鐵門和對應安裝專家
class IronDoorFactory implements DoorFactory {
public function makeDoor() : Door {
return new IronDoor();
}
public function makeFittingExpert() : DoorFittingExpert{
return new Welder();
}
}
然后可以這樣使用
$woodenFactory = new WoodenDoorFactory();
$door = $woodenFactory->makeDoor();
$expert = $woodenFactory->makeFittingExpert();
$door->getDescription(); // 輸出: I am a wooden door
$expert->getDescription(); // 輸出: I can only fit wooden doors
// 鐵門工廠也一樣
$ironFactory = new IronDoorFactory();
$door = $ironFactory->makeDoor();
$expert = $ironFactory->makeFittingExpert();
$door->getDescription(); // 輸出: I am an iron door
$expert->getDescription(); // 輸出: I can only fit iron doors
如你所見,木門工廠包含了木匠 carpenter
和木門 wooden door
而鐵門工廠包含了鐵門 iron door
和焊工 welder
。因此我們可以確保每扇制造出來的門不會帶上錯誤的安裝工。
何時使用?
當創建邏輯不那么簡單,而且相互之間有依賴時
?? 建造者模式
現實例子
想象你在麥當勞,你要一個“巨無霸”,他們馬上就給你了,沒有疑問,這是簡單工廠的邏輯。但如果創建邏輯包含更多步驟。比如你想要一個自定義賽百味套餐,你有多種選擇來制作漢堡,例如你要哪種面包?你要哪種調味醬?你要哪種奶酪?等。這種情況就需要建造者模式來處理。
白話
讓你能創建不同特點的對象而避免構造函數污染。當一個對象都多種特點的時候比較實用。或者在創造邏輯里有許多步驟的時候。
維基百科
The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.
話雖如此,讓我寫一點關于伸縮構造函數反面模式。在某些時候,我們都看過下面這樣的構造函數
public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true) {
}
如你所見;構造函數參數的數量馬上就要失去控制,而且梳理參數也會變得困難。而且如果你將來想要增加更多選項,參數也會繼續增加。這就叫做伸縮構造函數反面模式。
代碼例子
正常的做法是使用創建者模式。首先我們有了要做的漢堡
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;
}
}
然后我們有了制作者
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);
}
}
然后可以這樣使用
$burger = (new BurgerBuilder(14))
->addPepperoni()
->addLettuce()
->addTomato()
->build();
何時使用?
當對象有多種特性而要避免構造函數變長。和工廠模式的核心區別是;當創建過程只有一個步驟的時候使用工廠模式,而當創建過程有多個步驟的時候使用創造者模式。
?? 原型模式
現實例子
記得多利嗎?那只克隆羊!不要在意細節,現在的重點是克隆
白話
通過克隆已有的對象來創建新對象。
維基百科
The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.
長話短說,它讓你創建已有對象的拷貝,然后修改到你要的樣子,而不是從頭開始建造。
代碼例子
在 PHP 里,簡單的使用 clone
就可以了
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;
}
}
然后它可以被這樣克隆
$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
來改變克隆邏輯。
何時使用?
當一個對象需要跟已有的對象相似,或者當創造過程比起克隆來太昂貴時。
?? 單例模式
現實例子
一個國家同一時間只能有一個總統。當使命召喚的時候,這個總統要采取行動。這里的總統就是單例的。
白話
確保指定的類只生成一個對象。
維基百科
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
單例模式其實被看作一種反面模式,應該避免過度使用。它不一定不好,而且確有一些有效的用例,但是應該謹慎使用,因為它在你的應用里引入了全局狀態,在一個地方改變,會影響其他地方。而且很難 debug 。另一個壞處是它讓你的代碼緊耦合,而且很難仿制單例。
代碼例子
要創建一個單例,先讓構造函數私有,不能克隆,不能繼承,然后創造一個靜態變量來保存這個實例
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
}
}
然后要使用的話
$president1 = President::getInstance();
$president2 = President::getInstance();
var_dump($president1 === $president2); // true
結構型模式
白話
結構型模式更關注對象的組合,換句話說,實體如何彼此使用。或者說,它們幫助解答“如何建造軟件組件?”
維基百科
In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.
?? 適配器模式
現實例子
假設在你的存儲卡里有一些照片,你要把它們傳到電腦。為了傳輸,你需要一個兼容電腦端口的適配器來連接存儲卡和電腦。在這里,讀卡器就是一個適配器。
另一個例子是電源轉換器;一個三腳的插口不能插到兩口的插座上,它需要一個電源轉換器來兼容兩口的插座。
還有一個例子是翻譯將一個人說的話翻譯給另一個人。
白話
適配器模式讓你封裝一個不兼容的對象到一個適配器,來兼容其他類。
維基百科
In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.
代碼例子
假設一個獵人狩獵獅子的游戲。
首先我們有了一個接口獅子 Lion
來實現所有種類的獅子
interface Lion {
public function roar();
}
class AfricanLion implements Lion {
public function roar() {}
}
class AsianLion implements Lion {
public function roar() {}
}
以及獵人需要狩獵任何獅子 Lion
接口的實現。
class Hunter {
public function hunt(Lion $lion) {
}
}
現在我們不得不在游戲里加一個野狗 WildDog
,獵人也能狩獵它。但是我們不能直接這么做,因為狗有不同的接口。為了兼容我們的獵人,我們不得不創建一個兼容的適配器
// This needs to be added to the game
class WildDog {
public function bark() {}
}
// Adapter around wild dog to make it compatible with our game
class WildDogAdapter implements Lion {
protected $dog;
public function __construct(WildDog $dog) {
$this->dog = $dog;
}
public function roar() {
$this->dog->bark();
}
}
現在野狗 WildDog
可以在游戲里使用了,通過野狗適配器 WildDogAdapter
.
$wildDog = new WildDog();
$wildDogAdapter = new WildDogAdapter($wildDog);
$hunter = new Hunter();
$hunter->hunt($wildDogAdapter);
?? 橋接模式
現實例子
假設你有一個包含很多網頁的網站,你想要用戶可以改變主題。你會怎么做?創建每個頁面對應每個主題的拷備,還是只是創建不同的主題,然后根據用戶的喜好來加載它們?橋接模式讓你能做到后者。

白話
橋接模式傾向構造而非繼承。實現細節被從一個層推送到另一個對象的另一層。
維基百科
The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently"
代碼例子
翻譯我們上面的網頁例子。這里是網頁 WebPage
層
interface WebPage {
public function __construct(Theme $theme);
public function getContent();
}
class About implements WebPage {
protected $theme;
public function __construct(Theme $theme) {
$this->theme = $theme;
}
public function getContent() {
return "About page in " . $this->theme->getColor();
}
}
class Careers implements WebPage {
protected $theme;
public function __construct(Theme $theme) {
$this->theme = $theme;
}
public function getContent() {
return "Careers page in " . $this->theme->getColor();
}
}
以及主題層
interface Theme {
public function getColor();
}
class DarkTheme implements Theme {
public function getColor() {
return 'Dark Black';
}
}
class LightTheme implements Theme {
public function getColor() {
return 'Off white';
}
}
class AquaTheme implements Theme {
public function getColor() {
return 'Light blue';
}
}
兩個層的互動
$darkTheme = new DarkTheme();
$about = new About($darkTheme);
$careers = new Careers($darkTheme);
echo $about->getContent(); // "About page in Dark Black";
echo $careers->getContent(); // "Careers page in Dark Black";
?? 組合模式
現實例子
任何組織都是由員工組成。每個員工都有相同的特征,即一筆薪水,一些責任,可能需要向別人匯報,可能有一些下屬等。
白話
組合模式讓調用者可以用統一的模式對待不同的對象。
維基百科
In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.
代碼例子
拿上面的員工為例。下面是不同的員工類型
interface Employee {
public function __construct(string $name, float $salary);
public function getName() : string;
public function setSalary(float $salary);
public function getSalary() : float;
public function getRoles() : array;
}
class Developer implements Employee {
protected $salary;
protected $name;
public function __construct(string $name, float $salary) {
$this->name = $name;
$this->salary = $salary;
}
public function getName() : string {
return $this->name;
}
public function setSalary(float $salary) {
$this->salary = $salary;
}
public function getSalary() : float {
return $this->salary;
}
public function getRoles() : array {
return $this->roles;
}
}
class Designer implements Employee {
protected $salary;
protected $name;
public function __construct(string $name, float $salary) {
$this->name = $name;
$this->salary = $salary;
}
public function getName() : string {
return $this->name;
}
public function setSalary(float $salary) {
$this->salary = $salary;
}
public function getSalary() : float {
return $this->salary;
}
public function getRoles() : array {
return $this->roles;
}
}
下面是一個由不同類型員工組成的組織
class Organization {
protected $employees;
public function addEmployee(Employee $employee) {
$this->employees[] = $employee;
}
public function getNetSalaries() : float {
$netSalary = 0;
foreach ($this->employees as $employee) {
$netSalary += $employee->getSalary();
}
return $netSalary;
}
}
然后可以這樣使用
// 準備員工
$john = new Developer('John Doe', 12000);
$jane = new Designer('Jane', 10000);
// 把他們加到組織里去
$organization = new Organization();
$organization->addEmployee($john);
$organization->addEmployee($jane);
echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 22000
? 裝飾器模式
現實例子
想象你開一家汽車服務店,提供各種服務。現在你怎么計算收費?你選擇一個服務,然后不斷把價格加到已選服務的價格里,直到得到總價。這里,每種服務就是一個裝飾器。
白話
裝飾器模式讓你能在運行時動態地改變一個對象的表現,通過把它們封裝到一個裝飾器類。
維基百科
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.
代碼例子
讓我們以咖啡為例。首先我們有一個咖啡接口的簡單實現
interface Coffee {
public function getCost();
public function getDescription();
}
class SimpleCoffee implements Coffee {
public function getCost() {
return 10;
}
public function getDescription() {
return 'Simple coffee';
}
}
我們想要讓代碼可擴展,以在需要的時候改變選項。讓我們增加一些擴展(裝飾器)
class MilkCoffee implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost() + 2;
}
public function getDescription() {
return $this->coffee->getDescription() . ', milk';
}
}
class WhipCoffee implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost() + 5;
}
public function getDescription() {
return $this->coffee->getDescription() . ', whip';
}
}
class VanillaCoffee implements Coffee {
protected $coffee;
public function __construct(Coffee $coffee) {
$this->coffee = $coffee;
}
public function getCost() {
return $this->coffee->getCost() + 3;
}
public function getDescription() {
return $this->coffee->getDescription() . ', vanilla';
}
}
現在讓我們生成咖啡
$someCoffee = new SimpleCoffee();
echo $someCoffee->getCost(); // 10
echo $someCoffee->getDescription(); // Simple Coffee
$someCoffee = new MilkCoffee($someCoffee);
echo $someCoffee->getCost(); // 12
echo $someCoffee->getDescription(); // Simple Coffee, milk
$someCoffee = new WhipCoffee($someCoffee);
echo $someCoffee->getCost(); // 17
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip
$someCoffee = new VanillaCoffee($someCoffee);
echo $someCoffee->getCost(); // 20
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla
?? 門面模式
現實例子
你怎么打開電腦?你會說“按電源鍵”!你這么認為是因為你在用電腦外部提供的簡單接口,而在內部,它必須做很做工作來實現這件事。這個復雜子系統的簡單接口就是一個門面。
白話
門面模式提供了一個復雜子系統的簡單接口。
維基百科
A facade is an object that provides a simplified interface to a larger body of code, such as a class library.
代碼例子
拿上面電腦為例。下面是電腦類
class Computer {
public function getElectricShock() {
echo "Ouch!";
}
public function makeSound() {
echo "Beep beep!";
}
public function showLoadingScreen() {
echo "Loading..";
}
public function bam() {
echo "Ready to be used!";
}
public function closeEverything() {
echo "Bup bup bup buzzzz!";
}
public function sooth() {
echo "Zzzzz";
}
public function pullCurrent() {
echo "Haaah!";
}
}
下面是門面
class ComputerFacade
{
protected $computer;
public function __construct(Computer $computer) {
$this->computer = $computer;
}
public function turnOn() {
$this->computer->getElectricShock();
$this->computer->makeSound();
$this->computer->showLoadingScreen();
$this->computer->bam();
}
public function turnOff() {
$this->computer->closeEverything();
$this->computer->pullCurrent();
$this->computer->sooth();
}
}
如何使用門面
$computer = new ComputerFacade(new Computer());
$computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz
?? 享元模式
現實例子
你在小店里喝過茶嗎?他們經常比你要的多做幾杯,把剩下的留給別的客人,以此來省資源,比如煤氣。享元模式就是以上的體現,即分享。
白話
通過盡可能分享相似的對象,來將內存使用或計算開銷降到最低。
維基百科
In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.
代碼例子
翻譯上面的茶的例子。首先我們有了茶的類型和生成器
// 任何被緩存的東西都被叫做享元。
// 這里茶的類型就是享元。
class KarakTea {
}
// 像工廠一樣工作,保存茶
class TeaMaker {
protected $availableTea = [];
public function make($preference) {
if (empty($this->availableTea[$preference])) {
$this->availableTea[$preference] = new KarakTea();
}
return $this->availableTea[$preference];
}
}
下面是我們的茶吧 TeaShop
,接單和提供服務
class TeaShop {
protected $orders;
protected $teaMaker;
public function __construct(TeaMaker $teaMaker) {
$this->teaMaker = $teaMaker;
}
public function takeOrder(string $teaType, int $table) {
$this->orders[$table] = $this->teaMaker->make($teaType);
}
public function serve() {
foreach($this->orders as $table => $tea) {
echo "Serving tea to table# " . $table;
}
}
}
然后可以這樣使用
$teaMaker = new TeaMaker();
$shop = new TeaShop($teaMaker);
$shop->takeOrder('less sugar', 1);
$shop->takeOrder('more milk', 2);
$shop->takeOrder('without sugar', 5);
$shop->serve();
// Serving tea to table# 1
// Serving tea to table# 2
// Serving tea to table# 5
?? 代理模式
現實例子
你有沒有用過門卡來通過一扇門?有多種方式來打開那扇門,即它可以被門卡打開,或者按開門按鈕打開。這扇門的主要功能是開關,但在頂層增加了一個代理來增加其他功能。下面的例子能更好的說明。
白話
使用代理模式,一個類表現出了另一個類的功能。
維基百科
A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked.
代碼例子
拿上面安全門為例。首先我們有了門的接口和實現
interface Door {
public function open();
public function close();
}
class LabDoor implements Door {
public function open() {
echo "Opening lab door";
}
public function close() {
echo "Closing the lab door";
}
}
然后下面是一個代理來安保任何我們要的門
class Security {
protected $door;
public function __construct(Door $door) {
$this->door = $door;
}
public function open($password) {
if ($this->authenticate($password)) {
$this->door->open();
} else {
echo "Big no! It ain't possible.";
}
}
public function authenticate($password) {
return $password === '$ecr@t';
}
public function close() {
$this->door->close();
}
}
然后可以這樣使用
$door = new Security(new LabDoor());
$door->open('invalid'); // Big no! It ain't possible.
$door->open('$ecr@t'); // Opening lab door
$door->close(); // Closing lab door
另一個例子是一些數據映射的實現。比如,我最近用這個模式給 MongoDB 做了一個數據映射器 ODM (Object Data Mapper),我用魔術方法 __call()
給 mongo 類做了一個代理。所有執行的方法都被代理到原始的 mongo 類,返回收到的結果。但是在 find
或 findOne
的情況,數據被映射到對應的對象,這個對象會被返回,而不是 Cursor
。
行為型模式
白話
它關注對象間的責任分配。它們和結構型模式的區別是它們不止明確指明結構,而且指出了它們之間傳遞/交流的信息的形式。或者換句或說,它們幫助回答了“如何確定軟件組件的行為?”
維基百科
In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.
- 責任鏈模式 Chain of Responsibility
- 命令模式 Command
- 迭代器模式 Iterator
- 中介模式 Mediator
- 備忘錄模式 Memento
- 觀察者模式 Observer
- 訪問者模式 Visitor
- 策略模式 Strategy
- 狀態模式 State
- 模板模式 Template Method
?? 責任鏈模式
現實例子
比如,有三個支付方式 (
A
,B
和C
) 安裝在你的賬戶里;每種方式都有不同額度。A
有 100 元,B
有 300 元,以及C
有 1000 元,選擇支付方式的順序是A
然后B
然后C
。你要買一些價值 210 元的東西。使用責任鏈模式,首先賬戶A
會被檢查是否能夠支付,如果是,支付會被執行而鏈子終止。如果否,請求會轉移到賬戶B
,檢查額度,如果是,鏈子終止,否則請求繼續轉移直到找到合適的執行者。這里A
,B
和C
是鏈接里的環節,它們合起來就是責任鏈。
白話
它構造了一個對象的鏈。請求進入一端,然后從一個對象到另一個對象直到找到合適的執行者。
維基百科
In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
代碼例子
翻譯上面的賬戶例子。首先我們有了一個基本賬戶,包含把賬戶連接起來的邏輯。以及一些賬戶
abstract class Account {
protected $successor;
protected $balance;
public function setNext(Account $account) {
$this->successor = $account;
}
public function pay(float $amountToPay) {
if ($this->canPay($amountToPay)) {
echo sprintf('Paid %s using %s' . PHP_EOL, $amountToPay, get_called_class());
} else if ($this->successor) {
echo sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, get_called_class());
$this->successor->pay($amountToPay);
} else {
throw Exception('None of the accounts have enough balance');
}
}
public function canPay($amount) : bool {
return $this->balance >= $amount;
}
}
class Bank extends Account {
protected $balance;
public function __construct(float $balance) {
$this->balance = $balance;
}
}
class Paypal extends Account {
protected $balance;
public function __construct(float $balance) {
$this->balance = $balance;
}
}
class Bitcoin extends Account {
protected $balance;
public function __construct(float $balance) {
$this->balance = $balance;
}
}
現在我們用上面定義的環節(即銀行 Bank,貝寶 Paypal,比特幣 Bitcoin)準備鏈
// 我們準備下面這樣的鏈
// $bank->$paypal->$bitcoin
//
// 首選銀行 bank
// 如果銀行 bank 不能支付則選擇貝寶 paypal
// 如果貝寶 paypal 不能支付則選擇比特幣 bit coin
$bank = new Bank(100); // 銀行 Bank 有余額 100
$paypal = new Paypal(200); // 貝寶 Paypal 有余額 200
$bitcoin = new Bitcoin(300); // 比特幣 Bitcoin 有余額 300
$bank->setNext($paypal);
$paypal->setNext($bitcoin);
// 我們嘗試用首選項支付,即銀行 bank
$bank->pay(259);
// 輸出將會是
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!
?? 命令模式
現實例子
一個普遍的例子是你在餐館點餐。你 (即調用者
Client
) 要求服務員 (即調用器Invoker
) 端來一些食物 (即命令Command
),而服務員只是簡單的把命令傳達給知道怎么做菜的廚師 (即接收者Receiver
)。另一個例子是你 (即調用者Client
) 打開 (即命令Command
) 電視 (即接收者Receiver
),通過使用遙控 (調用器Invoker
).
白話
允許你封裝對象的功能。此模式的核心思想是分離調用者和接收者。
維基百科
In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
代碼例子
首先我們有一個接收者,包含了每一個可執行的功能的實現
// Receiver
class Bulb {
public function turnOn() {
echo "Bulb has been lit";
}
public function turnOff() {
echo "Darkness!";
}
}
然后下面是每個命令執行的接口,之后我們就有了一個命令的集合
interface Command {
public function execute();
public function undo();
public function redo();
}
// Command
class TurnOn implements Command {
protected $bulb;
public function __construct(Bulb $bulb) {
$this->bulb = $bulb;
}
public function execute() {
$this->bulb->turnOn();
}
public function undo() {
$this->bulb->turnOff();
}
public function redo() {
$this->execute();
}
}
class TurnOff implements Command {
protected $bulb;
public function __construct(Bulb $bulb) {
$this->bulb = $bulb;
}
public function execute() {
$this->bulb->turnOff();
}
public function undo() {
$this->bulb->turnOn();
}
public function redo() {
$this->execute();
}
}
然后我們有了一個執行器 Invoker
,調用者可以通過它執行命令
// Invoker
class RemoteControl {
public function submit(Command $command) {
$command->execute();
}
}
最后我們看看可以如何使用
$bulb = new Bulb();
$turnOn = new TurnOn($bulb);
$turnOff = new TurnOff($bulb);
$remote = new RemoteControl();
$remote->submit($turnOn); // Bulb has been lit!
$remote->submit($turnOff); // Darkness!
命令模式也可以用來實現一個基礎系統的事務。當你要一直在執行命令后馬上維護日志。如果命令被正確執行,一切正常,否則沿日志迭代,一直對每個已執行的命令執行撤銷 undo
。
? 迭代器模式
現實例子
老式調頻收音機是迭代器的好例子,用戶可以在一些頻道開始,然后使用前進或后退按鈕來瀏覽每個頻道。或者以 MP3 播放器或電視機為例,你可以按前進或后退按鈕來瀏覽連續的頻道。或者說,它們都提供了迭代連續的頻道,歌曲或廣播的接口。
白話
它提供了一種方式來獲得對象的元素,而不必暴露底層實現。
維基百科
In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.
代碼例子
在 PHP 里,用 SPL (標準 PHP 庫) 實現非常簡單。翻譯上面的廣播例子。首先我們有了廣播臺 RadioStation
class RadioStation {
protected $frequency;
public function __construct(float $frequency) {
$this->frequency = $frequency;
}
public function getFrequency() : float {
return $this->frequency;
}
}
下面是我們的迭代器
use Countable;
use Iterator;
class StationList implements Countable, Iterator {
/** @var RadioStation[] $stations */
protected $stations = [];
/** @var int $counter */
protected $counter;
public function addStation(RadioStation $station) {
$this->stations[] = $station;
}
public function removeStation(RadioStation $toRemove) {
$toRemoveFrequency = $toRemove->getFrequency();
$this->stations = array_filter($this->stations, function (RadioStation $station) use ($toRemoveFrequency) {
return $station->getFrequency() !== $toRemoveFrequency;
});
}
public function count() : int {
return count($this->stations);
}
public function current() : RadioStation {
return $this->stations[$this->counter];
}
public function key() {
return $this->counter;
}
public function next() {
$this->counter++;
}
public function rewind() {
$this->counter = 0;
}
public function valid(): bool
{
return isset($this->stations[$this->counter]);
}
}
然后可以這樣使用
$stationList = new StationList();
$stationList->addStation(new Station(89));
$stationList->addStation(new Station(101));
$stationList->addStation(new Station(102));
$stationList->addStation(new Station(103.2));
foreach($stationList as $station) {
echo $station->getFrequency() . PHP_EOL;
}
$stationList->removeStation(new Station(89)); // Will remove station 89
?? 中介模式
現實例子
一個普遍的例子是當你用手機和別人談話,你和別人中間隔了一個電信網,你的聲音穿過它而不是直接發出去。在這里,電信網就是一個中介。
白話
中介模式增加了一個第三方對象(叫做中介)來控制兩個對象(叫做同事)間的交互。它幫助減少類彼此之間交流的耦合度。因為它們現在不需要知道彼此的實現。
維基百科
In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior.
代碼例子
下面是一個最簡單的聊天室(即中介)的例子,用戶(即同事)彼此發送信息。
首先,我們有一個中介,即聊天室
// 中介
class ChatRoom implements ChatRoomMediator {
public function showMessage(User $user, string $message) {
$time = date('M d, y H:i');
$sender = $user->getName();
echo $time . '[' . $sender . ']:' . $message;
}
}
然后我們有用戶,即同事
class User {
protected $name;
protected $chatMediator;
public function __construct(string $name, ChatRoomMediator $chatMediator) {
$this->name = $name;
$this->chatMediator = $chatMediator;
}
public function getName() {
return $this->name;
}
public function send($message) {
$this->chatMediator->showMessage($this, $message);
}
}
然后是使用
$mediator = new ChatRoom();
$john = new User('John Doe', $mediator);
$jane = new User('Jane Doe', $mediator);
$john->send('Hi there!');
$jane->send('Hey!');
// 輸出將會是
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!
?? 備忘錄模式
現實例子
以計算器(即發起人)為例,無論什么時候你執行一些計算,最后的計算都會保存在內存(即備忘)里,這樣你就能返回到這里,并且用一些按鈕(即守護者)恢復。
白話
備忘錄模式捕捉和保存當前對象的狀態,然后用一種平滑的方式恢復。
維基百科
The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).
當你要提供撤銷方法時異常實用。
代碼例子
讓我們那編輯器為例,編輯器一直保存狀態,在你需要的時候可以恢復。
首先下面是我們的備忘錄對象,可以保存編輯器狀態
class EditorMemento {
protected $content;
public function __construct(string $content) {
$this->content = $content;
}
public function getContent() {
return $this->content;
}
}
然后是我們的編輯器,即發起者,來使用備忘錄對象
class Editor {
protected $content = '';
public function type(string $words) {
$this->content = $this->content . ' ' . $words;
}
public function getContent() {
return $this->content;
}
public function save() {
return new EditorMemento($this->content);
}
public function restore(EditorMemento $memento) {
$this->content = $memento->getContent();
}
}
然后可以這樣使用
$editor = new Editor();
// 輸入一些東西
$editor->type('This is the first sentence.');
$editor->type('This is second.');
// 保存狀態到:This is the first sentence. This is second.
$saved = $editor->save();
// 輸入些別的東西
$editor->type('And this is third.');
// 輸出: Content before Saving
echo $editor->getContent(); // This is the first sentence. This is second. And this is third.
// 恢復到上次保存狀態
$editor->restore($saved);
$editor->getContent(); // This is the first sentence. This is second.
?? 觀察者模式
現實例子
一個好的例子是求職者,他們訂閱了一些工作發布網站,當有合適的工作機會時,他們會收到提醒。
白話
定義了一個對象間的依賴,這樣無論何時一個對象改變了狀態,其他所有依賴者會收到提醒。
維基百科
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
代碼例子
翻譯上面的例子。首先我們有需要收到工作發布提醒的求職者
class JobPost {
protected $title;
public function __construct(string $title) {
$this->title = $title;
}
public function getTitle() {
return $this->title;
}
}
class JobSeeker implements Observer {
protected $name;
public function __construct(string $name) {
$this->name = $name;
}
public function onJobPosted(JobPost $job) {
// Do something with the job posting
echo 'Hi ' . $this->name . '! New job posted: '. $job->getTitle();
}
}
下面是求職者訂閱的工作信息
class JobPostings implements Observable {
protected $observers = [];
protected function notify(JobPost $jobPosting) {
foreach ($this->observers as $observer) {
$observer->onJobPosted($jobPosting);
}
}
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function addJob(JobPost $jobPosting) {
$this->notify($jobPosting);
}
}
然后可以這樣使用
// 創建訂閱者
$johnDoe = new JobSeeker('John Doe');
$janeDoe = new JobSeeker('Jane Doe');
$kaneDoe = new JobSeeker('Kane Doe');
// 創建發布者,綁定訂閱者
$jobPostings = new JobPostings();
$jobPostings->attach($johnDoe);
$jobPostings->attach($janeDoe);
// 添加一個工作,看訂閱者是否收到通知
$jobPostings->addJob(new JobPost('Software Engineer'));
// 輸出
// Hi John Doe! New job posted: Software Engineer
// Hi Jane Doe! New job posted: Software Engineer
?? 訪問者模式
現實例子
假設一些人訪問迪拜。他們需要一些方式(即簽證)來進入迪拜。抵達后,他們可以去迪拜的任何地方,而不用申請許可或者跑腿;他們知道的地方都可以去。訪問者模式可以讓你這樣做,它幫你添加可以訪問的地方,然后他們可以訪問盡可能多的地方而不用到處跑腿。
白話
訪問者模式可以讓你添加更多的操作到對象,而不用改變他們。
維基百科
In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.
代碼例子
讓我們以動物園模擬器為例,在里面我們有一些動物,我們必須讓他們叫。讓我們用訪問者模式來翻譯
// 被訪者
interface Animal {
public function accept(AnimalOperation $operation);
}
// 訪問者
interface AnimalOperation {
public function visitMonkey(Monkey $monkey);
public function visitLion(Lion $lion);
public function visitDolphin(Dolphin $dolphin);
}
Then we have our implementations for the animals
class Monkey implements Animal {
public function shout() {
echo 'Ooh oo aa aa!';
}
public function accept(AnimalOperation $operation) {
$operation->visitMonkey($this);
}
}
class Lion implements Animal {
public function roar() {
echo 'Roaaar!';
}
public function accept(AnimalOperation $operation) {
$operation->visitLion($this);
}
}
class Dolphin implements Animal {
public function speak() {
echo 'Tuut tuttu tuutt!';
}
public function accept(AnimalOperation $operation) {
$operation->visitDolphin($this);
}
}
實現我們的訪問者
class Speak implements AnimalOperation {
public function visitMonkey(Monkey $monkey) {
$monkey->shout();
}
public function visitLion(Lion $lion) {
$lion->roar();
}
public function visitDolphin(Dolphin $dolphin) {
$dolphin->speak();
}
}
然后可以這樣使用
$monkey = new Monkey();
$lion = new Lion();
$dolphin = new Dolphin();
$speak = new Speak();
$monkey->accept($speak); // Ooh oo aa aa!
$lion->accept($speak); // Roaaar!
$dolphin->accept($speak); // Tuut tutt tuutt!
我們本可以簡單地給動物加一個繼承層來做到這點,但是這樣每當我們要給動物增加新功能的時候,我們就不得不改變動物。但是現在我們不用改變他們。比如,我們要給動物增加一個跳的行為,我們可以通過簡單地增加一個新的訪問者
class Jump implements AnimalOperation {
public function visitMonkey(Monkey $monkey) {
echo 'Jumped 20 feet high! on to the tree!';
}
public function visitLion(Lion $lion) {
echo 'Jumped 7 feet! Back on the ground!';
}
public function visitDolphin(Dolphin $dolphin) {
echo 'Walked on water a little and disappeared';
}
}
然后這樣用
$jump = new Jump();
$monkey->accept($speak); // Ooh oo aa aa!
$monkey->accept($jump); // Jumped 20 feet high! on to the tree!
$lion->accept($speak); // Roaaar!
$lion->accept($jump); // Jumped 7 feet! Back on the ground!
$dolphin->accept($speak); // Tuut tutt tuutt!
$dolphin->accept($jump); // Walked on water a little and disappeared
?? 策略模式
現實例子
考慮排序的例子,我們實現了冒泡排序,但是數據開始增長,冒泡排序變得很慢。為了應對這個,我們實現了快速排序。但現在盡管快速排序算法對大數據集表現更好,小數據集卻很慢。為了應對這一點,我們實現一個策略,冒泡排序處理小數據集,快速排序處理大數據集。
白話
策略模式允許你基于情況選擇算法或策略。
維基百科
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioural software design pattern that enables an algorithm's behavior to be selected at runtime.
代碼例子
翻譯我們上面的例子。首先我們有了策略接口和不同的策略實現
interface SortStrategy {
public function sort(array $dataset) : array;
}
class BubbleSortStrategy implements SortStrategy {
public function sort(array $dataset) : array {
echo "Sorting using bubble sort";
// Do sorting
return $dataset;
}
}
class QuickSortStrategy implements SortStrategy {
public function sort(array $dataset) : array {
echo "Sorting using quick sort";
// Do sorting
return $dataset;
}
}
然后是實用策略的調用者
class Sorter {
protected $sorter;
public function __construct(SortStrategy $sorter) {
$this->sorter = $sorter;
}
public function sort(array $dataset) : array {
return $this->sorter->sort($dataset);
}
}
然后可以這樣使用
$dataset = [1, 5, 4, 3, 2, 8];
$sorter = new Sorter(new BubbleSortStrategy());
$sorter->sort($dataset); // 輸出 : Sorting using bubble sort
$sorter = new Sorter(new QuickSortStrategy());
$sorter->sort($dataset); // 輸出 : Sorting using quick sort
?? 狀態模式
現實例子
想象你在使用畫圖程序,你選擇筆刷來畫。現在筆刷根據選擇的顏色改變自己的行為。即如果你選擇紅色,它就用紅色畫,如果是藍色它就用藍色等等。
白話
他讓你能類的狀態改變時,改變其行為。
維基百科
The state pattern is a behavioral software design pattern that implements a state machine in an object-oriented way. With the state pattern, a state machine is implemented by implementing each individual state as a derived class of the state pattern interface, and implementing state transitions by invoking methods defined by the pattern's superclass.
The state pattern can be interpreted as a strategy pattern which is able to switch the current strategy through invocations of methods defined in the pattern's interface.
代碼例子
讓我們以編輯器作為例子,它能讓你改變文本的狀態,比如你選擇了加粗,它開始以加粗字體書寫,如果選擇傾斜,就以傾斜字體等等。
首先,我們有狀態接口和一些狀態實現
interface WritingState {
public function write(string $words);
}
class UpperCase implements WritingState {
public function write(string $words) {
echo strtoupper($words);
}
}
class LowerCase implements WritingState {
public function write(string $words) {
echo strtolower($words);
}
}
class Default implements WritingState {
public function write(string $words) {
echo $words;
}
}
下面是我們的編輯器
class TextEditor {
protected $state;
public function __construct(WritingState $state) {
$this->state = $state;
}
public function setState(WritingState $state) {
$this->state = $state;
}
public function type(string $words) {
$this->state->write($words);
}
}
然后可以這樣使用
$editor = new TextEditor(new Default());
$editor->type('First line');
$editor->setState(new UpperCaseState());
$editor->type('Second line');
$editor->type('Third line');
$editor->setState(new LowerCaseState());
$editor->type('Fourth line');
$editor->type('Fifth line');
// 輸出:
// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line
?? 模板模式
現實例子
假設我們要建房子。建造的步驟類似這樣
- 準備房子的地基
- 建造墻
- 建造房頂
- 然后是地板
這些步驟步驟的順序永遠不會變,即你不能在建墻之前建屋頂,當時每個步驟都可以改變,比如墻可以是木頭可以是聚酯或者石頭。
白話
模板模式定義了一個算法會如何執行的骨架,但把這些步驟的實現移交給子類。
維基百科
In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure.
代碼例子
想象我們有一個構建工具幫我們測試,糾錯,構建,生成構建報告(即代碼報告,查錯報告),然后把應用發布到測試服務器。
首先是我們的基礎類,它描述了構建算法的骨架
abstract class Builder {
// Template method
public final function build() {
$this->test();
$this->lint();
$this->assemble();
$this->deploy();
}
public abstract function test();
public abstract function lint();
public abstract function assemble();
public abstract function deploy();
}
以下是實現
class AndroidBuilder extends Builder {
public function test() {
echo 'Running android tests';
}
public function lint() {
echo 'Linting the android code';
}
public function assemble() {
echo 'Assembling the android build';
}
public function deploy() {
echo 'Deploying android build to server';
}
}
class IosBuilder extends Builder {
public function test() {
echo 'Running ios tests';
}
public function lint() {
echo 'Linting the ios code';
}
public function assemble() {
echo 'Assembling the ios build';
}
public function deploy() {
echo 'Deploying ios build to server';
}
}
然后可以這樣使用
$androidBuilder = new AndroidBuilder();
$androidBuilder->build();
// 輸出:
// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server
$iosBuilder = new IosBuilder();
$iosBuilder->build();
// 輸出:
// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server
?? 收尾了同志們
終于收尾了。我會繼續改進這篇文檔,所以你或許需要 watch/star 這個倉庫,先碼后看。
?? Contribution
- Report issues
- Open pull request with improvements
- Spread the word
翻譯
License
MIT ? Kamran Ahmed