設計模式超級簡單的解釋

guanguans發表於2019-03-06

image

推薦閱讀


設計模式超簡單的解釋!(本專案從 design-patterns-for-humans fork)


設計模式是反覆出現問題的解決方案; 如何解決某些問題的指導方針。它們不是可以插入應用程式並等待神奇發生的類,包或庫。相反,這些是如何在某些情況下解決某些問題的指導原則。

設計模式是反覆出現問題的解決方案; 如何解決某些問題的指導方針

維基百科將它們描述為

在軟體工程中,軟體設計模式是軟體設計中給定上下文中常見問題的通用可重用解決方案。它不是可以直接轉換為原始碼或機器程式碼的完成設計。它是如何解決可在許多不同情況下使用的問題的描述或模板。

?注意

  • 設計模式不是解決所有問題的靈丹妙藥。
  • 不要試圖強迫他們; 如果這樣做的話,應該發生壞事。
  • 請記住,設計模式是問題的解決方案,而不是解決問題的解決方案;所以不要過分思考。
  • 如果以正確的方式在正確的地方使用,他們可以證明是救世主; 否則他們可能會導致程式碼混亂。

另請注意,下面的程式碼示例是PHP-7,但是這不應該阻止你因為概念是相同的。

設計模式的型別

簡單來說

建立模式專注於如何例項化物件或相關物件組。

維基百科說

在軟體工程中,建立設計模式是處理物件建立機制的設計模式,試圖以適合於該情況的方式建立物件。物件建立的基本形式可能導致設計問題或增加設計的複雜性。建立設計模式通過某種方式控制此物件建立來解決此問題。

?簡單工廠模式(Simple Factory)

現實世界的例子

考慮一下,你正在建房子,你需要門。你可以穿上你的木匠衣服,帶上一些木頭,膠水,釘子和建造門所需的所有工具,然後開始在你的房子裡建造它,或者你可以簡單地打電話給工廠並把內建的門送到你這裡不需要了解關於制門的任何資訊或處理製作它所帶來的混亂。

簡單來說

簡單工廠只是為客戶端生成一個例項,而不會向客戶端公開任何例項化邏輯

維基百科說

在物件導向程式設計(OOP)中,工廠是用於建立其他物件的物件 - 正式工廠是一種函式或方法,它從一些方法呼叫返回變化的原型或類的物件,這被假定為“新”。

程式化示例

首先,我們有一個門介面和實現

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);
    }
}

然後它可以用作

// Make me a door of 100x200
$door = DoorFactory::makeDoor(100, 200);

echo 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();

// Make me a door of 50x100
$door2 = DoorFactory::makeDoor(50, 100);

什麼時候用?

當建立一個物件不僅僅是一些分配而且涉及一些邏輯時,將它放在專用工廠中而不是在任何地方重複相同的程式碼是有意義的。

?工廠方法模式(Factory Method)

現實世界的例子

考慮招聘經理的情況。一個人不可能對每個職位進行面試。根據職位空缺,她必須決定並將面試步驟委託給不同的人。

簡單來說

它提供了一種將例項化邏輯委託給子類的方法。

維基百科說

在基於類的程式設計中,工廠方法模式是一種建立模式,它使用工廠方法來處理建立物件的問題,而無需指定將要建立的物件的確切類。這是通過呼叫工廠方法來建立物件來完成的 - 在介面中指定並由子類實現,或者在基類中實現並可選地由派生類覆蓋 - 而不是通過呼叫建構函式。

程式化示例

以上面的招聘經理為例。首先,我們有一個訪談者介面和一些實現

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 protected function makeInterviewer(): Interviewer;

    public function takeInterview()
    {
        $interviewer = $this->makeInterviewer();
        $interviewer->askQuestions();
    }
}

現在任何孩子都可以延長並提供所需的面試官

class DevelopmentManager extends HiringManager
{
    protected function makeInterviewer(): Interviewer
    {
        return new Developer();
    }
}

class MarketingManager extends HiringManager
{
    protected 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.

什麼時候用?

在類中有一些通用處理但在執行時動態決定所需的子類時很有用。換句話說,當客戶端不知道它可能需要什麼樣的子類時。

?抽象工廠模式(Abstract Factory)

現實世界的例子

從Simple Factory擴充套件我們的門例子。根據您的需求,您可以從木門店,鐵門的鐵門或相關商店的PVC門獲得木門。另外,你可能需要一個有不同種類特色的傢伙來安裝門,例如木門木匠,鐵門焊機等。你可以看到門之間存在依賴關係,木門需要木匠,鐵門需要焊工等

簡單來說

工廠工廠; 將個人但相關/依賴工廠分組在一起而不指定其具體類別的工廠。

維基百科說

抽象工廠模式提供了一種封裝一組具有共同主題但沒有指定其具體類的單個工廠的方法

程式化示例

翻譯上面的門例子。首先,我們有我們的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;
}

// 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();
    }
}

然後它可以用作

$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

正如你所看到的木門工廠的封裝carpenterwooden door還鐵門廠已封裝的iron doorwelder。因此,它幫助我們確保對於每個建立的門,我們沒有得到錯誤的擬合專家。

什麼時候用?

當存在相互關聯的依賴關係時,涉及非簡單的建立邏輯

?構建器模式

現實世界的例子

想象一下,你在Hardee's,你訂購了一個特定的交易,讓我們說,“Big Hardee”,他們毫無_疑問地_把它交給你了; 這是簡單工廠的例子。但有些情況下,建立邏輯可能涉及更多步驟。例如,你想要一個定製的地鐵交易,你有多種選擇如何製作你的漢堡,例如你想要什麼麵包?你想要什麼型別的醬汁?你想要什麼乳酪?在這種情況下,建築商模式得以拯救。

簡單來說

允許您建立不同風格的物件,同時避免建構函式汙染。當有幾種風格的物體時很有用。或者在建立物件時涉及很多步驟。

維基百科說

構建器模式是物件建立軟體設計模式,其目的是找到伸縮構造器反模式的解決方案。

話雖如此,讓我補充說一下伸縮建構函式反模式是什麼。在某一點或另一點,我們都看到了如下建構函式:

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();

什麼時候用?

當可能存在幾種型別的物件並避免建構函式伸縮時。與工廠模式的主要區別在於:當建立是一步過程時,將使用工廠模式,而當建立是多步驟過程時,將使用構建器模式。

?原型模式(Prototype)

現實世界的例子

記得多莉?被克隆的羊!讓我們不詳細介紹,但關鍵點在於它完全是關於克隆的

簡單來說

通過克隆基於現有物件建立物件。

維基百科說

原型模式是軟體開發中的創新設計模式。當要建立的物件型別由原型例項確定時使用它,該例項被克隆以生成新物件。

簡而言之,它允許您建立現有物件的副本並根據需要進行修改,而不是從頭開始建立物件並進行設定。

程式化示例

在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來修改克隆行為。

什麼時候用?

當需要一個與現有物件類似的物件時,或者與克隆相比,建立的成本會很高。

?單例模式(Singleton)

現實世界的例子

一次只能有一個國家的總統。無論何時打電話,都必須將同一位總統付諸行動。這裡的總統是單身人士。

簡單來說

確保只建立特定類的一個物件。

維基百科說

在軟體工程中,單例模式是一種軟體設計模式,它將類的例項化限制為一個物件。當需要一個物件來協調整個系統的操作時,這非常有用。

單例模式實際上被認為是反模式,應該避免過度使用它。它不一定是壞的,可能有一些有效的用例,但應謹慎使用,因為它在您的應用程式中引入了一個全域性狀態,並且在一個地方更改它可能會影響其他區域,並且它可能變得非常難以除錯。關於它們的另一個壞處是它使你的程式碼緊密耦合加上嘲弄單例可能很困難。

程式化示例

要建立單例,請將建構函式設為私有,禁用克隆,禁用擴充套件並建立靜態變數以容納例項

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

簡單來說

結構模式主要涉及物件組成,或者換句話說,實體如何相互使用。或者另一種解釋是,它們有助於回答“如何構建軟體元件?”

維基百科說

在軟體工程中,結構設計模式是通過識別實現實體之間關係的簡單方法來簡化設計的設計模式。

?介面卡模式(Adapter)

現實世界的例子

請注意,您的儲存卡中有一些照片,需要將它們傳輸到計算機上。為了傳輸它們,您需要某種與您的計算機埠相容的介面卡,以便您可以將儲存卡連線到您的計算機。在這種情況下,讀卡器是介面卡。另一個例子是著名的電源介面卡; 三腳插頭不能連線到雙管插座,需要使用電源介面卡,使其與雙叉插座相容。另一個例子是翻譯人員將一個人所說的話翻譯成另一個人

簡單來說

介面卡模式允許您在介面卡中包裝其他不相容的物件,以使其與另一個類相容。

維基百科說

在軟體工程中,介面卡模式是一種軟體設計模式,它允許將現有類的介面用作另一個介面。它通常用於使現有類與其他類一起工作而無需修改其原始碼。

程式化示例

考慮一個有獵人的遊戲,他獵殺獅子。

首先,我們有一個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)
    {
        $lion->roar();
    }
}

現在讓我們說我們必須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);

?橋樑模式(Bridge)

現實世界的例子

考慮您有一個包含不同頁面的網站,您應該允許使用者更改主題。你會怎麼做?為每個主題建立每個頁面的多個副本,或者您只是建立單獨的主題並根據使用者的首選項載入它們?橋模式允許你做第二個ie

bridge.png

用簡單的話說

橋模式是關於優先於繼承的組合。實現細節從層次結構推送到具有單獨層次結構的另一個物件。

維基百科說

橋接模式是軟體工程中使用的設計模式,旨在“將抽象與其實現分離,以便兩者可以獨立變化”

程式化示例

從上面翻譯我們的WebPage示例。這裡我們有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";

?組合模式(Composite)

現實世界的例子

每個組織都由員工組成。每個員工都有相同的功能,即有工資,有一些責任,可能會或可能不會向某人報告,可能會或可能不會有一些下屬等。

簡單來說

複合模式允許客戶以統一的方式處理單個物件。

維基百科說

在軟體工程中,複合模式是分割槽設計模式。複合模式描述了一組物件的處理方式與物件的單個例項相同。複合的意圖是將物件“組合”成樹結構以表示部分整體層次結構。通過實現複合模式,客戶可以統一處理單個物件和組合。

程式化示例

以上面的員工為例。這裡我們有不同的員工型別

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;
    protected $roles;

    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;
    protected $roles;

    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;
    }
}

然後它可以用作

// 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

☕裝飾模式(Decorator)

現實世界的例子

想象一下,您經營一家提供多種服務的汽車服務店。現在你如何計算收費賬單?您選擇一項服務並動態地向其新增所提供服務的價格,直到您獲得最終成本。這裡的每種服務都是裝飾者。

簡單來說

Decorator模式允許您通過將物件包裝在裝飾器類的物件中來動態更改物件在執行時的行為。

維基百科說

在物件導向的程式設計中,裝飾器模式是一種設計模式,它允許將行為靜態或動態地新增到單個物件,而不會影響同一類中其他物件的行為。裝飾器模式通常用於遵守單一責任原則,因為它允許在具有獨特關注區域的類之間劃分功能。

程式化示例

讓我們以咖啡為例。首先,我們有一個簡單的咖啡實現咖啡介面

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

?門面模式(Facade)

現實世界的例子

你怎麼開啟電腦?“按下電源按鈕”你說!這就是你所相信的,因為你正在使用計算機在外部提供的簡單介面,在內部它必須做很多事情來實現它。這個複雜子系統的簡單介面是一個外觀。

簡單來說

Facade模式為複雜的子系統提供了簡化的介面。

維基百科說

外觀是一個物件,它為更大的程式碼體提供了簡化的介面,例如類庫。

程式化示例

從上面看我們的計算機示例。這裡我們有電腦課

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

?享元模式(Flyweight)

現實世界的例子

你有沒有從一些攤位買到新鮮的茶?他們經常製作你需要的不止一個杯子,併為其他任何客戶儲存其餘的,以節省資源,例如天然氣等.Flyweight模式就是那個即共享。

簡單來說

它用於通過儘可能多地與類似物件共享來最小化記憶體使用或計算開銷。

維基百科說

在計算機程式設計中,flyweight是一種軟體設計模式。flyweight是一個通過與其他類似物件共享盡可能多的資料來最小化記憶體使用的物件; 當簡單的重複表示將使用不可接受的記憶體量時,它是一種大量使用物件的方法。

程式化的例子

從上面翻譯我們的茶例子。首先,我們有茶類和茶具

// 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];
    }
}

然後我們有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

?代理模式(Proxy)

現實世界的例子

你有沒有用過門禁卡進門?開啟該門有多種選擇,即可以使用門禁卡或按下繞過安檢的按鈕開啟。門的主要功能是開啟,但在它上面新增了一個代理來新增一些功能。讓我用下面的程式碼示例更好地解釋它。

簡單來說

使用代理模式,類表示另一個類的功能。

維基百科說

代理以其最一般的形式,是一個充當其他東西的介面的類。代理是一個包裝器或代理物件,客戶端正在呼叫它來訪問幕後的真實服務物件。使用代理可以簡單地轉發到真實物件,或者可以提供額外的邏輯。在代理中,可以提供額外的功能,例如當對真實物件的操作是資源密集時的快取記憶體,或者在呼叫物件的操作之前檢查先決條件。

程式化示例

從上面看我們的安全門示例。首先我們有門介面和門的實現

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 SecuredDoor
{
    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 SecuredDoor(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(物件資料對映器),我在使用魔術方法的同時圍繞mongo類編寫了一個代理__call()。所有方法呼叫被代理到原始蒙戈類和結果檢索到的返回,因為它是但在的情況下,findfindOne資料被對映到所需的類物件和物件返回代替Cursor

簡單來說

它關注物件之間的職責分配。它們與結構模式的不同之處在於它們不僅指定了結構,還概述了它們之間的訊息傳遞/通訊模式。或者換句話說,他們協助回答“如何在軟體元件中執行行為?”

維基百科說

在軟體工程中,行為設計模式是識別物件之間的共同通訊模式並實現這些模式的設計模式。通過這樣做,這些模式增加了執行該通訊的靈活性。

?責任鏈模式(Chain Of Responsibilities)

現實世界的例子

例如,你有三種付款方式(ABC)安裝在您的帳戶; 每個都有不同的數量。A有100美元,B具有300美元和C具有1000美元,以及支付偏好被選擇作為AB然後C。你試著購買價值210美元的東西。使用責任鏈,首先A會檢查帳戶是否可以進行購買,如果是,則進行購買並且鏈條將被破壞。如果沒有,請求將繼續進行帳戶B檢查金額,如果是鏈將被破壞,否則請求將繼續轉發,直到找到合適的處理程式。在這裡ABC 是鏈條的連結,整個現象是責任鏈。

簡單來說

它有助於構建一系列物件。請求從一端進入並繼續從一個物件到另一個物件,直到找到合適的處理程式。

維基百科說

在物件導向的設計中,責任鏈模式是一種由命令物件源和一系列處理物件組成的設計模式。每個處理物件都包含定義它可以處理的命令物件型別的邏輯; 其餘的傳遞給鏈中的下一個處理物件。

程式化示例

翻譯上面的帳戶示例。首先,我們有一個基本帳戶,其中包含將帳戶連結在一起的邏輯和一些帳戶

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());
        } elseif ($this->successor) {
            echo sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, get_called_class());
            $this->successor->pay($amountToPay);
        } else {
            throw new 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)

// Let's prepare a chain like below
//      $bank->$paypal->$bitcoin
//
// First priority bank
//      If bank can't pay then paypal
//      If paypal can't pay then bit coin

$bank = new Bank(100);          // Bank with balance 100
$paypal = new Paypal(200);      // Paypal with balance 200
$bitcoin = new Bitcoin(300);    // Bitcoin with balance 300

$bank->setNext($paypal);
$paypal->setNext($bitcoin);

// Let's try to pay using the first priority i.e. bank
$bank->pay(259);

// Output will be
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!

?命令列模式(Command)

現實世界的例子

一個通用的例子是你在餐廳點餐。您(即Client)要求服務員(即Invoker)攜帶一些食物(即Command),服務員只是將請求轉發給主廚(即Receiver),該主廚知道什麼以及如何烹飪。另一個例子是你(即)使用遙控器()Client開啟(即Command)電視(即)。Receiver``Invoker

簡單來說

允許您將操作封裝在物件中。這種模式背後的關鍵思想是提供將客戶端與接收器分離的方法。

維基百科說

在物件導向的程式設計中,命令模式是行為設計​​模式,其中物件用於封裝執行動作或稍後觸發事件所需的所有資訊。此資訊包括方法名稱,擁有該方法的物件以及方法引數的值。

程式化示例

首先,我們有接收器,它可以執行每個可以執行的操作

// 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所有已執行的命令。

➿迭代器模式(Iterator)

現實世界的例子

舊的無線電裝置將是迭代器的一個很好的例子,使用者可以從某個頻道開始,然後使用下一個或上一個按鈕來瀏覽相應的頻道。或者以MP3播放器或電視機為例,您可以按下下一個和上一個按鈕來瀏覽連續的頻道,換句話說,它們都提供了一個介面來迭代各自的頻道,歌曲或電臺。

簡單來說

它提供了一種訪問物件元素而不暴露底層表示的方法。

維基百科說

在物件導向的程式設計中,迭代器模式是一種設計模式,其中迭代器用於遍歷容器並訪問容器的元素。迭代器模式將演算法與容器分離; 在某些情況下,演算法必然是特定於容器的,因此不能解耦。

程式化的例子

在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 RadioStation(89));
$stationList->addStation(new RadioStation(101));
$stationList->addStation(new RadioStation(102));
$stationList->addStation(new RadioStation(103.2));

foreach($stationList as $station) {
    echo $station->getFrequency() . PHP_EOL;
}

$stationList->removeStation(new RadioStation(89)); // Will remove station 89

?中介者模式(Mediator)

現實世界的例子

一個典型的例子就是當你在手機上與某人交談時,有一個網路提供商坐在你和他們之間,你的對話通過它而不是直接傳送。在這種情況下,網路提供商是中介。

簡單來說

Mediator模式新增第三方物件(稱為mediator)來控制兩個物件(稱為同事)之間的互動。它有助於減少彼此通訊的類之間的耦合。因為現在他們不需要了解彼此的實施。

維基百科說

在軟體工程中,中介模式定義了一個物件,該物件封裝了一組物件的互動方式。由於它可以改變程式的執行行為,因此這種模式被認為是一種行為模式。

程式化示例

這是聊天室(即中介)與使用者(即同事)相互傳送訊息的最簡單示例。

首先,我們有調解員即聊天室

interface ChatRoomMediator 
{
    public function showMessage(User $user, string $message);
}

// Mediator
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!');

// Output will be
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!

?備忘錄模式(Memento)

現實世界的例子

以計算器(即發起者)為例,無論何時執行某些計算,最後的計算都會儲存在記憶體中(即紀念品),以便您可以回到它並使用某些操作按鈕(即看管人)恢復它。

簡單來說

Memento模式是關於以稍後可以以平滑方式恢復的方式捕獲和儲存物件的當前狀態。

維基百科說

memento模式是一種軟體設計模式,它提供將物件恢復到其先前狀態的能力(通過回滾撤消)。

當您需要提供某種撤消功能時通常很有用。

程式化示例

讓我們舉一個文字編輯器的例子,它不時地儲存狀態,你可以根據需要恢復。

首先,我們有memento物件,可以儲存編輯器狀態

class EditorMemento
{
    protected $content;

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function getContent()
    {
        return $this->content;
    }
}

然後我們有我們的編輯器即即將使用memento物件的創作者

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();

// Type some stuff
$editor->type('This is the first sentence.');
$editor->type('This is second.');

// Save the state to restore to : This is the first sentence. This is second.
$saved = $editor->save();

// Type some more
$editor->type('And this is third.');

// Output: Content before Saving
echo $editor->getContent(); // This is the first sentence. This is second. And this is third.

// Restoring to last saved state
$editor->restore($saved);

$editor->getContent(); // This is the first sentence. This is second.

?觀察者模式(Observer)

現實世界的例子

一個很好的例子是求職者,他們訂閱了一些職位釋出網站,只要有匹配的工作機會,他們就會得到通知。

簡單來說

定義物件之間的依賴關係,以便每當物件更改其狀態時,都會通知其所有依賴項。

維基百科說

觀察者模式是一種軟體設計模式,其中一個稱為主體的物件維護其依賴者列表,稱為觀察者,並通常通過呼叫其中一種方法自動通知它們任何狀態變化。

程式化的例子

從上面翻譯我們的例子。首先,我們有求職者需要通知職位釋出

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 EmploymentAgency 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);
    }
}

然後它可以用作

// Create subscribers
$johnDoe = new JobSeeker('John Doe');
$janeDoe = new JobSeeker('Jane Doe');

// Create publisher and attach subscribers
$jobPostings = new EmploymentAgency();
$jobPostings->attach($johnDoe);
$jobPostings->attach($janeDoe);

// Add a new job and see if subscribers get notified
$jobPostings->addJob(new JobPost('Software Engineer'));

// Output
// Hi John Doe! New job posted: Software Engineer
// Hi Jane Doe! New job posted: Software Engineer

?訪問者模式(Visitor)

現實世界的例子

考慮去杜拜的人。他們只需要一種方式(即簽證)進入杜拜。抵達後,他們可以自己來杜拜的任何地方,而無需徵求許可或做一些腿部工作,以便訪問這裡的任何地方; 讓他們知道一個地方,他們可以訪問它。訪客模式可以讓您做到這一點,它可以幫助您新增訪問的地方,以便他們可以儘可能多地訪問,而無需做任何腿部工作。

簡單來說

訪客模式允許您向物件新增更多操作,而無需修改它們。

維基百科說

在物件導向的程式設計和軟體工程中,訪問者設計模式是一種將演算法與其執行的物件結構分離的方法。這種分離的實際結果是能夠在不修改這些結構的情況下向現有物件結構新增新操作。這是遵循開放/封閉原則的一種方式。

程式化的例子

讓我們舉一個動物園模擬的例子,我們有幾種不同的動物,我們必須讓它們成為聲音。讓我們用訪客模式翻譯這個

// Visitee
interface Animal
{
    public function accept(AnimalOperation $operation);
}

// Visitor
interface AnimalOperation
{
    public function visitMonkey(Monkey $monkey);
    public function visitLion(Lion $lion);
    public function visitDolphin(Dolphin $dolphin);
}

然後我們有動物實施

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

?策略模式(Strategy)

現實世界的例子

考慮排序的例子,我們實現了氣泡排序,但資料開始增長,氣泡排序開始變得非常緩慢。為了解決這個問題,我們實現了快速排序。但是現在雖然快速排序演算法對大型資料集的效果更好,但對於較小的資料集來說速度非常慢。為了解決這個問題,我們實施了一個策略,對於小型資料集,將使用氣泡排序並進行更大規模的快速排序。

簡單來說

策略模式允許您根據情況切換演算法或策略。

維基百科說

在計算機程式設計中,策略模式(也稱為策略模式)是一種行為軟體設計模式,可以在執行時選擇演算法的行為。

程式化的例子

從上面翻譯我們的例子。首先,我們有戰略介面和不同的戰略實施

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);
    }
}

它可以用作

And it can be used as

$dataset = [1, 5, 4, 3, 2, 8];

$sorter = new Sorter(new BubbleSortStrategy());
$sorter->sort($dataset); // Output : Sorting using bubble sort

$sorter = new Sorter(new QuickSortStrategy());
$sorter->sort($dataset); // Output : Sorting using quick sort

?狀態模式(State)

現實世界的例子

想象一下,你正在使用一些繪圖應用程式,你選擇繪製畫筆。現在畫筆根據所選顏色改變其行為,即如果你選擇了紅色,它會畫成紅色,如果是藍色則會是藍色等。

簡單來說

它允許您在狀態更改時更改類的行為。

維基百科說

狀態模式是一種行為軟體設計模式,它以物件導向的方式實現狀態機。使用狀態模式,通過將每個單獨的狀態實現為狀態模式介面的派生類來實現狀態機,並通過呼叫由模式的超類定義的方法來實現狀態轉換。狀態模式可以解釋為一種策略模式,它能夠通過呼叫模式介面中定義的方法來切換當前策略。

程式化的例子

讓我們以文字編輯器為例,它允許您更改鍵入的文字的狀態,即如果您選擇了粗體,則開始以粗體顯示,如果是斜體,則以斜體顯示等。

首先,我們有狀態介面和一些狀態實現

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 DefaultText 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 DefaultText());

$editor->type('First line');

$editor->setState(new UpperCase());

$editor->type('Second line');
$editor->type('Third line');

$editor->setState(new LowerCase());

$editor->type('Fourth line');
$editor->type('Fifth line');

// Output:
// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line

?模板方法模式(Template Method)

現實世界的例子

假設我們正在建造一些房屋。構建的步驟可能看起來像

  • 準備房子的基地
  • 建造牆壁
  • 新增屋頂
  • 新增其他樓層

這些步驟的順序永遠不會改變,即在建造牆壁等之前不能建造屋頂,但是每個步驟都可以修改,例如牆壁可以由木頭或聚酯或石頭製成。

簡單來說

模板方法定義瞭如何執行某個演算法的框架,但是將這些步驟的實現推遲到子類。

維基百科說

在軟體工程中,模板方法模式是一種行為設計模式,它定義了操作中演算法的程式框架,將一些步驟推遲到子類。它允許重新定義演算法的某些步驟而不改變演算法的結構。

程式化示例

想象一下,我們有一個構建工具,可以幫助我們測試,lint,構建,生成構建報告(即程式碼覆蓋率報告,linting報告等),並在測試伺服器上部署我們的應用程式。

首先,我們有基類,它指定構建演算法的骨架

abstract class Builder
{

    // Template method
    final public function build()
    {
        $this->test();
        $this->lint();
        $this->assemble();
        $this->deploy();
    }

    abstract public function test();
    abstract public function lint();
    abstract public function assemble();
    abstract public 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();

// Output:
// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server

$iosBuilder = new IosBuilder();
$iosBuilder->build();

// Output:
// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server

?總結一下

那就是把它包起來。我將繼續改進這一點,因此您可能希望觀看/加註此儲存庫以重新訪問。此外,我計劃對架構模式進行相同的編寫,請繼續關注它。

No practice, no gain in one's wit.
我的 Gitub

相關文章