用最簡單的語言,解釋設計模式。
雖然示例程式碼是用 PHP7 實現的,但因為概念是一樣的,所以語言並不會阻礙大家理解設計模式。
結構型設計模式
概述
結構型設計模式主要關注物件組合,換句話說,關注實體之間如何互相使用。 或者還有另外一個解釋,結構型設計模式有助於回答“如何構建軟體元件?”
維基百科
在軟體工程中,結構型設計模式是藉由一以貫之的方式來了解元件間的關係,從而簡化設計的一種設計模式。
分類
- 介面卡模式
- 橋接模式
- 組合模式
- 修飾模式
- 外觀模式
- 享元模式
- 代理模式
? 介面卡模式
現實生活示例
考慮這樣一個場景,你的儲存卡中有一些照片,你需要將其傳輸到計算機。為此,你需要某種與計算機埠相容的介面卡,以便將儲存卡連線到計算機上。在這種情況下,讀卡器就是介面卡。另外一個例子就是大名鼎鼎的電源介面卡:一個三腳插頭不能連線到雙插頭插座,需要使用電源介面卡使其與雙插頭插座相容。另外一個例子是譯者將一個人說的話翻譯給另一個人。
概述
介面卡模式可以將不相容的物件包裝成介面卡來適配其它類。
維基百科
在軟體工程中,介面卡模式是允許將現有類的介面用作另一個類介面的軟體設計模式。它通常用於現有類與其他類的協作,而無需修改現有類的程式碼。
程式示例
考慮一個遊戲場景,有一個獵人,他狩獵獅子。
首先給出 Lion
介面,所有種類的獅子都要實現這個介面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
interface Lion { public function roar(); } class AfricanLion implements Lion { public function roar() { } } class AsianLion implements Lion { public function roar() { } } |
獵人希望可以狩獵任何實現 Lion
介面的獅子
1 2 3 4 5 6 |
class Hunter { public function hunt(Lion $lion) { } } |
現在我們假定獵人在遊戲中也可以狩獵野狗
。但是目前我們無法實現,因為狗是通過其他介面實現。為了讓獵人可以狩獵野狗,我們需要建立一個介面卡,來相容這種情況。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 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(); } } |
這樣,在遊戲中通過 WildDogAdapter
就可以使用 WildDog
1 2 3 4 5 |
$wildDog = new WildDog(); $wildDogAdapter = new WildDogAdapter($wildDog); $hunter = new Hunter(); $hunter->hunt($wildDogAdapter); |
? 橋接模式
現實生活示例
假如你有一個網站,上面有不同的網頁,並且允許使用者更改主題。你會如何實現呢?是為每個頁面的各個主題建立多個副本,還是建立單獨的主題,並根據使用者的偏好來載入主題呢?橋接模式可以幫你實現後者。
不使用橋接模式
使用橋接模式
概述
橋接模式主打的是組合優於繼承。實現細節從物件的層次結構推送給具有單獨層次結構的另一個物件。
維基百科
橋接模式是軟體工程中使用的設計模式,旨在“將抽象與實現分離,使得兩者可以獨立變化”
程式示例
以上面提到的網頁為例,下面是 WebPage
的結構
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 |
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(); } } |
獨立的 主題
結構
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 |
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'; } } |
都是兩層結構
1 2 3 4 5 6 7 |
$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"; |
? 組合模式
現實生活示例
每個公司都是由員工組成,每個員工都有一些共同特徵比如薪資以及所承擔的某些責任,會或者不會向其他人彙報工作,有或者沒有下屬等。
概述
組合模式讓客戶端以統一的方式對待各個物件。
維基百科
在軟體工程中,組合模式是一種分治設計模式。組合模式對待一組物件的處理方式與對待物件的單個例項相同。組合的意圖是將物件“組合”成樹狀結構以呈現部分-整體的層次結構。實現組合模式可以使客戶端能夠均勻地處理單個物件和組合。
程式示例
以上面提到的員工為例,下面是不同的員工型別
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 |
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; } } |
包含幾種不同型別員工的公司
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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; } } |
然後可以這樣呼叫
1 2 3 4 5 6 7 8 9 10 |
// Prepare the employees $john = new Developer('John Doe', 12000); $jane = new Designer('Jane Doe', 15000); // Add them to organization $organization = new Organization(); $organization->addEmployee($john); $organization->addEmployee($jane); echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 27000 |
☕ 裝飾器模式
現實生活示例
想象一下,你在經營一家提供多種服務的汽車服務站。現在如何計算收費帳單呢?選擇一項服務,並動態地向其新增價格,直到獲得最終成本。這裡每種型別的服務就是裝飾器。
概述
通過將物件包裝在裝飾器類的物件中,裝飾器模式可以在執行時動態地更改物件的行為。
維基百科
裝飾器模式,是物件導向程式設計領域中,一種動態地或靜態地往一個類中新增新行為而不影響相同類中其他物件的設計模式。裝飾器模式對於遵守單一責任原則通常是有用的,因為它允許在具有獨特領域的類之間劃分功能。
程式示例
我們以咖啡為例,首先我們通過咖啡介面實現一個簡單咖啡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
interface Coffee { public function getCost(); public function getDescription(); } class SimpleCoffee implements Coffee { public function getCost() { return 10; } public function getDescription() { return 'Simple coffee'; } } |
我們希望程式碼可擴充套件,以允許選項在需要時進行修改。 增加一些附加項(裝飾器)
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 |
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'; } } |
下面我們來做杯咖啡吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$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 |
? 外觀模式
現實生活示例
請問你會如何開啟計算機呢?你會回答:“按電源鍵就行!”。你會這樣想是因為你在使用計算機對外提供的簡易介面,但是在內部,計算機完成了很多工作後才得以啟動,這種複雜子系統的簡單介面就是外觀模式。
概述
外觀模式提供了一個簡化複雜系統的簡單介面。
維基百科
外觀模式是指標對像類庫這種大體積程式碼提供簡化介面的物件。
程式示例
以上面提到的計算機為例,給出計算機類
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 |
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!"; } } |
下面是計算機的外觀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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(); } } |
可以這樣使用外觀模式
1 2 3 |
$computer = new ComputerFacade(new Computer()); $computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used! $computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz |
? 享元模式
現實生活示例
你是否在某個攤位上喝過茶?店主總是會多做一些茶,以預留給其他顧客,以此來節省資源比如燃氣。享元模式所講的就是共享。
概述
享元模式通過相似物件之間儘可能的資源共享,來最小化記憶體使用或計算開銷。
維基百科
在計算機程式設計中,享元模式是一種軟體設計模式。享元模式是通過與其他類似物件共享盡可能多的資料來最小化記憶體使用的物件; 當簡單的重複物件過多佔用記憶體時,可以通過享元模式來處理大量相似物件的情況。
程式示例
以茶為例,首先定義茶的種類及茶具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Anything that will be cached is flyweight. // Types of tea here will be flyweights. class KarakTea { } // Acts as a factory and saves the tea class TeaMaker { protected $availableTea = []; public function make($preference) { if (empty($this->availableTea[$preference])) { $this->availableTea[$preference] = new KarakTea(); } return $this->availableTea[$preference]; } } |
然後定義茶店來接單及提供服務
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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; } } } |
下面可以這樣使用
1 2 3 4 5 6 7 8 9 10 11 |
$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 |
? 代理模式
現實生活示例
有沒有過使用門禁卡進門的經歷呢?開啟門的方法有多種,既可以使用門禁卡,也可以按下門上的安全按鈕。門的主要功能是開啟,但在其上新增一個代理,便可以給門新增一些功能。下面的程式碼示例可以給出更好的解釋。
概述
使用代理模式,一個類可以代理另外一個類的功能。
維基百科
一個代理,其最一般的形式,是一個作為其他類介面的類。代理是由客戶端呼叫的包裝器或代理物件,用來訪問幕後的真實服務物件。使用代理可以簡單地向真實物件做轉發,或者可以提供額外的邏輯。在代理模式中,可以提供額外的功能,例如當在真實物件上的操作是資源密集型時進行快取,或者在呼叫真實物件的操作之前進行預處理。
程式示例
以安全門為例,首先給出安全門介面及實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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"; } } |
然後使用代理來確保門的安全
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 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(); } } |
可以這樣使用
1 2 3 4 5 |
$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(物件資料對映器),其中我使用魔術方法__call()
在 mongo 類上編寫代理。所有的方法呼叫被代理到原始的 mongo 類,並且檢索的結果原樣返回,但如果呼叫 find
或 findOne
,資料會被對映到所需的類物件,並且返回了物件而不是Cursor
。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!