用人話介紹設(shè)計(jì)模式

Design Patterns For Humans
Design Patterns For Humans

?? 對(duì)設(shè)計(jì)模式的極簡(jiǎn)說(shuō)明丹擎!??

這個(gè)話題可以輕易讓任何人糊涂∥惨郑現(xiàn)在我嘗試通過(guò)用<i>最簡(jiǎn)單</i>的方式說(shuō)明它們,來(lái)讓你(和我)把他們吃透蒂培。


?? 簡(jiǎn)介

設(shè)計(jì)模式用來(lái)解決重復(fù)的問(wèn)題再愈;是解決特定問(wèn)題的指導(dǎo)方針。它們不是類(class)护戳,包(packages)翎冲,或者庫(kù)(libraries),你不能引入它們媳荒,然后等待奇跡發(fā)生抗悍。它們是針對(duì)解決特定環(huán)境下特定問(wèn)題的指導(dǎo)方針。

設(shè)計(jì)模式用來(lái)解決重復(fù)的問(wèn)題钳枕;是解決特定問(wèn)題的指導(dǎo)方針

維基百科的解釋

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.

?? 請(qǐng)注意

  • 設(shè)計(jì)模式不是解決你所有問(wèn)題的銀彈缴渊。
  • 不要嘗試強(qiáng)行使用它們;如果做了鱼炒,不好的事情可能發(fā)生衔沼。請(qǐng)記住設(shè)計(jì)模式是解決問(wèn)題的方案,不是發(fā)現(xiàn)問(wèn)題昔瞧;所以不要過(guò)度思考指蚁。
  • 如果在正確的地方以正確的方式使用,它們被證明是有幫助的硬爆;否則結(jié)果可能是一堆可怕混亂的代碼欣舵。

下面的代碼示例使用 PHP-7 書(shū)寫(xiě),但你不應(yīng)止步于此缀磕,因?yàn)槔砟钍窍嗤ǖ脑等ΑT偌由?s對(duì)其他語(yǔ)言的支持正在路上劣光。

設(shè)計(jì)模式的種類

創(chuàng)建型模式

白話

創(chuàng)建型模式側(cè)重如何實(shí)例化一個(gè)對(duì)象或一組相關(guān)對(duì)象。

維基百科

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.

?? 簡(jiǎn)單工廠模式

現(xiàn)實(shí)例子

假設(shè)糟把,你正在建造一所房子绢涡,你需要門(mén)。如果每次你需要一扇門(mén)你都要穿上木工服開(kāi)始在房子里造扇門(mén)遣疯,將會(huì)是一團(tuán)亂雄可。取而代之的是讓工廠造好。

白話

簡(jiǎn)單工廠模式在不暴露生成邏輯的前提下生成一個(gè)實(shí)例缠犀。

維基百科

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".

代碼例子

首先数苫,我們有一個(gè)門(mén)的接口和實(shí)現(xiàn)

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

然后,我們有了工廠來(lái)制造和返回門(mén)

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

什么時(shí)候使用辨液?

當(dāng)創(chuàng)建一個(gè)對(duì)象不只是幾個(gè)賦值和邏輯計(jì)算虐急,把這件工作交給一個(gè)工廠而不是到處重復(fù)相同的代碼就比較合適了。

?? 工廠方法模式

現(xiàn)實(shí)例子

設(shè)想一個(gè)人事經(jīng)理滔迈。一個(gè)人是不可能面試所有職位的止吁。基于職位空缺燎悍,她必須把面試委托給不同的人敬惦。

白話

它提供了一個(gè)把生成邏輯移交給子類的方法。

維基百科

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.

代碼例子

以上面的人事經(jīng)理為例。首先我們有一個(gè)面試官接口和一些實(shí)現(xiàn)

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

現(xiàn)在我們新建我們的人事經(jīng)理 HiringManager

abstract class HiringManager {
    
    // Factory method
    abstract public function makeInterviewer() : Interviewer;
    
    public function takeInterview() {
        $interviewer = $this->makeInterviewer();
        $interviewer->askQuestions();
    }
}

現(xiàn)在任何一個(gè)都可以繼承它,并且生成需要的面試官

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.

何時(shí)使用参歹?

當(dāng)一個(gè)類里有普遍性的處理過(guò)程,但是子類要在運(yùn)行時(shí)才確定抗蠢【儆矗或者換句話說(shuō)思劳,調(diào)用者不知道它需要哪個(gè)子類。

?? 抽象工廠模式

現(xiàn)實(shí)例子

擴(kuò)展我們簡(jiǎn)單工廠模式的例子妨猩∏迸眩基于你的需求,你可以從木門(mén)店得到一扇木門(mén)壶硅,從鐵門(mén)店得到一扇鐵門(mén)威兜,或者從塑料門(mén)店得到一扇塑料門(mén)。而且你需要一個(gè)有不同專長(zhǎng)的人來(lái)安裝這扇門(mén)庐椒,比如一個(gè)木匠來(lái)安木門(mén)椒舵,焊工來(lái)安鐵門(mén)等。正如你看的约谈,門(mén)和安裝工有依賴性笔宿,木門(mén)需要木匠犁钟,鐵門(mén)需要焊工等。

白話

一個(gè)制造工廠的工廠泼橘;一個(gè)工廠把獨(dú)立但是相關(guān)/有依賴性的工廠進(jìn)行分類涝动,但是不需要給出具體的類。

維基百科

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes

代碼例子

翻譯上面門(mén)的例子炬灭。首先我們有了門(mén) Door 的接口和一些實(shí)現(xiàn)

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

然后我們有了每種門(mén)的安裝專家

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

現(xiàn)在我們有了抽象工廠來(lái)創(chuàng)建全部相關(guān)的對(duì)象醋粟,即木門(mén)工廠制造木門(mén)和木門(mén)安裝專家,鐵門(mén)工廠制造鐵門(mén)和鐵門(mén)安裝專家

interface DoorFactory {
    public function makeDoor() : Door;
    public function makeFittingExpert() : DoorFittingExpert;
}

// 木頭工廠返回木門(mén)和木匠
class WoodenDoorFactory implements DoorFactory {
    public function makeDoor() : Door {
        return new WoodenDoor();
    }

    public function makeFittingExpert() : DoorFittingExpert{
        return new Carpenter();
    }
}

// 鐵門(mén)工廠返回鐵門(mén)和對(duì)應(yīng)安裝專家
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

// 鐵門(mén)工廠也一樣
$ironFactory = new IronDoorFactory();

$door = $ironFactory->makeDoor();
$expert = $ironFactory->makeFittingExpert();

$door->getDescription();  // 輸出: I am an iron door
$expert->getDescription(); // 輸出: I can only fit iron doors

如你所見(jiàn)重归,木門(mén)工廠包含了木匠 carpenter 和木門(mén) wooden door 而鐵門(mén)工廠包含了鐵門(mén) iron door 和焊工 welder米愿。因此我們可以確保每扇制造出來(lái)的門(mén)不會(huì)帶上錯(cuò)誤的安裝工。

何時(shí)使用鼻吮?

當(dāng)創(chuàng)建邏輯不那么簡(jiǎn)單吗货,而且相互之間有依賴時(shí)

?? 建造者模式

現(xiàn)實(shí)例子

想象你在麥當(dāng)勞,你要一個(gè)“巨無(wú)霸”狈网,他們馬上就給你了宙搬,沒(méi)有疑問(wèn),這是簡(jiǎn)單工廠的邏輯拓哺。但如果創(chuàng)建邏輯包含更多步驟勇垛。比如你想要一個(gè)自定義賽百味套餐,你有多種選擇來(lái)制作漢堡士鸥,例如你要哪種面包闲孤?你要哪種調(diào)味醬?你要哪種奶酪烤礁?等讼积。這種情況就需要建造者模式來(lái)處理。

白話

讓你能創(chuàng)建不同特點(diǎn)的對(duì)象而避免構(gòu)造函數(shù)污染脚仔。當(dāng)一個(gè)對(duì)象都多種特點(diǎn)的時(shí)候比較實(shí)用勤众。或者在創(chuàng)造邏輯里有許多步驟的時(shí)候鲤脏。

維基百科

The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.

話雖如此们颜,讓我寫(xiě)一點(diǎn)關(guān)于伸縮構(gòu)造函數(shù)反面模式。在某些時(shí)候猎醇,我們都看過(guò)下面這樣的構(gòu)造函數(shù)

public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true) {
}

如你所見(jiàn)窥突;構(gòu)造函數(shù)參數(shù)的數(shù)量馬上就要失去控制,而且梳理參數(shù)也會(huì)變得困難硫嘶。而且如果你將來(lái)想要增加更多選項(xiàng)阻问,參數(shù)也會(huì)繼續(xù)增加。這就叫做伸縮構(gòu)造函數(shù)反面模式沦疾。

代碼例子

正常的做法是使用創(chuàng)建者模式称近。首先我們有了要做的漢堡

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

何時(shí)使用贡蓖?

當(dāng)對(duì)象有多種特性而要避免構(gòu)造函數(shù)變長(zhǎng)。和工廠模式的核心區(qū)別是煌茬;當(dāng)創(chuàng)建過(guò)程只有一個(gè)步驟的時(shí)候使用工廠模式斥铺,而當(dāng)創(chuàng)建過(guò)程有多個(gè)步驟的時(shí)候使用創(chuàng)造者模式。

?? 原型模式

現(xiàn)實(shí)例子

記得多利嗎坛善?那只克隆羊晾蜘!不要在意細(xì)節(jié),現(xiàn)在的重點(diǎn)是克隆

白話

通過(guò)克隆已有的對(duì)象來(lái)創(chuàng)建新對(duì)象眠屎。

維基百科

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.

長(zhǎng)話短說(shuō)剔交,它讓你創(chuàng)建已有對(duì)象的拷貝,然后修改到你要的樣子改衩,而不是從頭開(kāi)始建造岖常。

代碼例子

在 PHP 里,簡(jiǎn)單的使用 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 來(lái)改變克隆邏輯葫督。

何時(shí)使用竭鞍?

當(dāng)一個(gè)對(duì)象需要跟已有的對(duì)象相似,或者當(dāng)創(chuàng)造過(guò)程比起克隆來(lái)太昂貴時(shí)橄镜。

?? 單例模式

現(xiàn)實(shí)例子

一個(gè)國(guó)家同一時(shí)間只能有一個(gè)總統(tǒng)偎快。當(dāng)使命召喚的時(shí)候,這個(gè)總統(tǒng)要采取行動(dòng)洽胶。這里的總統(tǒng)就是單例的晒夹。

白話

確保指定的類只生成一個(gè)對(duì)象。

維基百科

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.

單例模式其實(shí)被看作一種反面模式姊氓,應(yīng)該避免過(guò)度使用丐怯。它不一定不好,而且確有一些有效的用例翔横,但是應(yīng)該謹(jǐn)慎使用读跷,因?yàn)樗谀愕膽?yīng)用里引入了全局狀態(tài),在一個(gè)地方改變棕孙,會(huì)影響其他地方舔亭。而且很難 debug 。另一個(gè)壞處是它讓你的代碼緊耦合蟀俊,而且很難仿制單例。

代碼例子

要?jiǎng)?chuàng)建一個(gè)單例订雾,先讓構(gòu)造函數(shù)私有肢预,不能克隆,不能繼承洼哎,然后創(chuàng)造一個(gè)靜態(tài)變量來(lái)保存這個(gè)實(shí)例

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

結(jié)構(gòu)型模式

白話

結(jié)構(gòu)型模式更關(guān)注對(duì)象的組合烫映,換句話說(shuō)沼本,實(shí)體如何彼此使用《Ч担或者說(shuō)抽兆,它們幫助解答“如何建造軟件組件?”

維基百科

In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.

?? 適配器模式

現(xiàn)實(shí)例子

假設(shè)在你的存儲(chǔ)卡里有一些照片族淮,你要把它們傳到電腦辫红。為了傳輸,你需要一個(gè)兼容電腦端口的適配器來(lái)連接存儲(chǔ)卡和電腦祝辣。在這里贴妻,讀卡器就是一個(gè)適配器。
另一個(gè)例子是電源轉(zhuǎn)換器蝙斜;一個(gè)三腳的插口不能插到兩口的插座上名惩,它需要一個(gè)電源轉(zhuǎn)換器來(lái)兼容兩口的插座。
還有一個(gè)例子是翻譯將一個(gè)人說(shuō)的話翻譯給另一個(gè)人孕荠。

白話

適配器模式讓你封裝一個(gè)不兼容的對(duì)象到一個(gè)適配器娩鹉,來(lái)兼容其他類。

維基百科

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.

代碼例子

假設(shè)一個(gè)獵人狩獵獅子的游戲稚伍。

首先我們有了一個(gè)接口獅子 Lion 來(lái)實(shí)現(xiàn)所有種類的獅子

interface Lion {
    public function roar();
}

class AfricanLion implements Lion {
    public function roar() {}
}

class AsianLion implements Lion {
    public function roar() {}
}

以及獵人需要狩獵任何獅子 Lion 接口的實(shí)現(xiàn)底循。

class Hunter {
    public function hunt(Lion $lion) {
    }
}

現(xiàn)在我們不得不在游戲里加一個(gè)野狗 WildDog ,獵人也能狩獵它槐瑞。但是我們不能直接這么做熙涤,因?yàn)楣酚胁煌慕涌凇榱思嫒菸覀兊墨C人困檩,我們不得不創(chuàng)建一個(gè)兼容的適配器

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

現(xiàn)在野狗 WildDog 可以在游戲里使用了祠挫,通過(guò)野狗適配器 WildDogAdapter.

$wildDog = new WildDog();
$wildDogAdapter = new WildDogAdapter($wildDog);

$hunter = new Hunter();
$hunter->hunt($wildDogAdapter);

?? 橋接模式

現(xiàn)實(shí)例子

假設(shè)你有一個(gè)包含很多網(wǎng)頁(yè)的網(wǎng)站,你想要用戶可以改變主題悼沿。你會(huì)怎么做等舔?創(chuàng)建每個(gè)頁(yè)面對(duì)應(yīng)每個(gè)主題的拷備,還是只是創(chuàng)建不同的主題糟趾,然后根據(jù)用戶的喜好來(lái)加載它們慌植?橋接模式讓你能做到后者。

With and without the bridge pattern
With and without the bridge pattern

白話

橋接模式傾向構(gòu)造而非繼承义郑。實(shí)現(xiàn)細(xì)節(jié)被從一個(gè)層推送到另一個(gè)對(duì)象的另一層蝶柿。

維基百科

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"

代碼例子

翻譯我們上面的網(wǎng)頁(yè)例子。這里是網(wǎng)頁(yè) 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';
    }
}

兩個(gè)層的互動(dòng)

$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";

?? 組合模式

現(xiàn)實(shí)例子

任何組織都是由員工組成非驮。每個(gè)員工都有相同的特征交汤,即一筆薪水,一些責(zé)任劫笙,可能需要向別人匯報(bào)芙扎,可能有一些下屬等星岗。

白話

組合模式讓調(diào)用者可以用統(tǒng)一的模式對(duì)待不同的對(duì)象。

維基百科

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

下面是一個(gè)由不同類型員工組成的組織

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

然后可以這樣使用

// 準(zhǔn)備員工
$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

? 裝飾器模式

現(xiàn)實(shí)例子

想象你開(kāi)一家汽車服務(wù)店俏橘,提供各種服務(wù)。現(xiàn)在你怎么計(jì)算收費(fèi)圈浇?你選擇一個(gè)服務(wù)寥掐,然后不斷把價(jià)格加到已選服務(wù)的價(jià)格里,直到得到總價(jià)汉额。這里曹仗,每種服務(wù)就是一個(gè)裝飾器。

白話

裝飾器模式讓你能在運(yùn)行時(shí)動(dòng)態(tài)地改變一個(gè)對(duì)象的表現(xiàn)蠕搜,通過(guò)把它們封裝到一個(gè)裝飾器類怎茫。

維基百科

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.

代碼例子

讓我們以咖啡為例。首先我們有一個(gè)咖啡接口的簡(jiǎn)單實(shí)現(xiàn)

interface Coffee {
    public function getCost();
    public function getDescription();
}

class SimpleCoffee implements Coffee {

    public function getCost() {
        return 10;
    }

    public function getDescription() {
        return 'Simple coffee';
    }
}

我們想要讓代碼可擴(kuò)展妓灌,以在需要的時(shí)候改變選項(xiàng)轨蛤。讓我們?cè)黾右恍U(kuò)展(裝飾器)

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

現(xiàn)在讓我們生成咖啡

$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

?? 門(mén)面模式

現(xiàn)實(shí)例子

你怎么打開(kāi)電腦?你會(huì)說(shuō)“按電源鍵”虫埂!你這么認(rèn)為是因?yàn)槟阍谟秒娔X外部提供的簡(jiǎn)單接口祥山,而在內(nèi)部,它必須做很做工作來(lái)實(shí)現(xiàn)這件事掉伏。這個(gè)復(fù)雜子系統(tǒng)的簡(jiǎn)單接口就是一個(gè)門(mén)面缝呕。

白話

門(mén)面模式提供了一個(gè)復(fù)雜子系統(tǒng)的簡(jiǎn)單接口。

維基百科

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

下面是門(mén)面

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

如何使用門(mén)面

$computer = new ComputerFacade(new Computer());
$computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz

?? 享元模式

現(xiàn)實(shí)例子

你在小店里喝過(guò)茶嗎供常?他們經(jīng)常比你要的多做幾杯,把剩下的留給別的客人鸡捐,以此來(lái)省資源栈暇,比如煤氣。享元模式就是以上的體現(xiàn)箍镜,即分享源祈。

白話

通過(guò)盡可能分享相似的對(duì)象,來(lái)將內(nèi)存使用或計(jì)算開(kāi)銷降到最低色迂。

維基百科

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 ,接單和提供服務(wù)

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

?? 代理模式

現(xiàn)實(shí)例子

你有沒(méi)有用過(guò)門(mén)卡來(lái)通過(guò)一扇門(mén)馏慨?有多種方式來(lái)打開(kāi)那扇門(mén)埂淮,即它可以被門(mén)卡打開(kāi),或者按開(kāi)門(mén)按鈕打開(kāi)写隶。這扇門(mén)的主要功能是開(kāi)關(guān)倔撞,但在頂層增加了一個(gè)代理來(lái)增加其他功能。下面的例子能更好的說(shuō)明慕趴。

白話

使用代理模式痪蝇,一個(gè)類表現(xiàn)出了另一個(gè)類的功能。

維基百科

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.

代碼例子

拿上面安全門(mén)為例冕房。首先我們有了門(mén)的接口和實(shí)現(xiàn)

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

然后下面是一個(gè)代理來(lái)安保任何我們要的門(mén)

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

另一個(gè)例子是一些數(shù)據(jù)映射的實(shí)現(xiàn)躏啰。比如,我最近用這個(gè)模式給 MongoDB 做了一個(gè)數(shù)據(jù)映射器 ODM (Object Data Mapper)耙册,我用魔術(shù)方法 __call() 給 mongo 類做了一個(gè)代理给僵。所有執(zhí)行的方法都被代理到原始的 mongo 類,返回收到的結(jié)果详拙。但是在 findfindOne 的情況帝际,數(shù)據(jù)被映射到對(duì)應(yīng)的對(duì)象,這個(gè)對(duì)象會(huì)被返回饶辙,而不是 Cursor蹲诀。

行為型模式

白話

它關(guān)注對(duì)象間的責(zé)任分配。它們和結(jié)構(gòu)型模式的區(qū)別是它們不止明確指明結(jié)構(gòu)弃揽,而且指出了它們之間傳遞/交流的信息的形式脯爪。或者換句或說(shuō)矿微,它們幫助回答了“如何確定軟件組件的行為痕慢?”

維基百科

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.

?? 責(zé)任鏈模式

現(xiàn)實(shí)例子

比如,有三個(gè)支付方式 (A, BC) 安裝在你的賬戶里冷冗;每種方式都有不同額度守屉。A 有 100 元, B 有 300 元蒿辙,以及 C 有 1000 元拇泛,選擇支付方式的順序是 A 然后 B 然后 C。你要買(mǎi)一些價(jià)值 210 元的東西思灌。使用責(zé)任鏈模式俺叭,首先賬戶 A 會(huì)被檢查是否能夠支付,如果是泰偿,支付會(huì)被執(zhí)行而鏈子終止熄守。如果否,請(qǐng)求會(huì)轉(zhuǎn)移到賬戶 B,檢查額度裕照,如果是攒发,鏈子終止,否則請(qǐng)求繼續(xù)轉(zhuǎn)移直到找到合適的執(zhí)行者晋南。這里 A惠猿,BC 是鏈接里的環(huán)節(jié),它們合起來(lái)就是責(zé)任鏈负间。

白話

它構(gòu)造了一個(gè)對(duì)象的鏈偶妖。請(qǐng)求進(jìn)入一端,然后從一個(gè)對(duì)象到另一個(gè)對(duì)象直到找到合適的執(zhí)行者政溃。

維基百科

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.

代碼例子

翻譯上面的賬戶例子趾访。首先我們有了一個(gè)基本賬戶,包含把賬戶連接起來(lái)的邏輯董虱。以及一些賬戶

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

現(xiàn)在我們用上面定義的環(huán)節(jié)(即銀行 Bank扼鞋,貝寶 Paypal,比特幣 Bitcoin)準(zhǔn)備鏈

// 我們準(zhǔn)備下面這樣的鏈
//      $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);

// 我們嘗試用首選項(xiàng)支付空扎,即銀行 bank
$bank->pay(259);

// 輸出將會(huì)是
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..: 
// Paid 259 using Bitcoin!

?? 命令模式

現(xiàn)實(shí)例子

一個(gè)普遍的例子是你在餐館點(diǎn)餐藏鹊。你 (即調(diào)用者 Client) 要求服務(wù)員 (即調(diào)用器 Invoker) 端來(lái)一些食物 (即命令 Command),而服務(wù)員只是簡(jiǎn)單的把命令傳達(dá)給知道怎么做菜的廚師 (即接收者 Receiver)转锈。另一個(gè)例子是你 (即調(diào)用者 Client) 打開(kāi) (即命令 Command) 電視 (即接收者 Receiver)盘寡,通過(guò)使用遙控 (調(diào)用器 Invoker).

白話

允許你封裝對(duì)象的功能。此模式的核心思想是分離調(diào)用者和接收者撮慨。

維基百科

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.

代碼例子

首先我們有一個(gè)接收者竿痰,包含了每一個(gè)可執(zhí)行的功能的實(shí)現(xiàn)

// Receiver
class Bulb {
    public function turnOn() {
        echo "Bulb has been lit";
    }
    
    public function turnOff() {
        echo "Darkness!";
    }
}

然后下面是每個(gè)命令執(zhí)行的接口,之后我們就有了一個(gè)命令的集合

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

然后我們有了一個(gè)執(zhí)行器 Invoker砌溺,調(diào)用者可以通過(guò)它執(zhí)行命令

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

命令模式也可以用來(lái)實(shí)現(xiàn)一個(gè)基礎(chǔ)系統(tǒng)的事務(wù)影涉。當(dāng)你要一直在執(zhí)行命令后馬上維護(hù)日志。如果命令被正確執(zhí)行规伐,一切正常蟹倾,否則沿日志迭代,一直對(duì)每個(gè)已執(zhí)行的命令執(zhí)行撤銷 undo 猖闪。

? 迭代器模式

現(xiàn)實(shí)例子

老式調(diào)頻收音機(jī)是迭代器的好例子鲜棠,用戶可以在一些頻道開(kāi)始,然后使用前進(jìn)或后退按鈕來(lái)瀏覽每個(gè)頻道培慌』砺剑或者以 MP3 播放器或電視機(jī)為例,你可以按前進(jìn)或后退按鈕來(lái)瀏覽連續(xù)的頻道吵护『幸簦或者說(shuō)表鳍,它們都提供了迭代連續(xù)的頻道,歌曲或廣播的接口祥诽。

白話

它提供了一種方式來(lái)獲得對(duì)象的元素譬圣,而不必暴露底層實(shí)現(xiàn)。

維基百科

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 (標(biāo)準(zhǔn) PHP 庫(kù)) 實(shí)現(xiàn)非常簡(jiǎn)單胁镐。翻譯上面的廣播例子偎血。首先我們有了廣播臺(tái) 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

?? 中介模式

現(xiàn)實(shí)例子

一個(gè)普遍的例子是當(dāng)你用手機(jī)和別人談話诸衔,你和別人中間隔了一個(gè)電信網(wǎng),你的聲音穿過(guò)它而不是直接發(fā)出去颇玷。在這里笨农,電信網(wǎng)就是一個(gè)中介。

白話

中介模式增加了一個(gè)第三方對(duì)象(叫做中介)來(lái)控制兩個(gè)對(duì)象(叫做同事)間的交互帖渠。它幫助減少類彼此之間交流的耦合度谒亦。因?yàn)樗鼈儸F(xiàn)在不需要知道彼此的實(shí)現(xiàn)。

維基百科

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.

代碼例子

下面是一個(gè)最簡(jiǎn)單的聊天室(即中介)的例子空郊,用戶(即同事)彼此發(fā)送信息份招。

首先,我們有一個(gè)中介狞甚,即聊天室

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

// 輸出將會(huì)是
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!

?? 備忘錄模式

現(xiàn)實(shí)例子

以計(jì)算器(即發(fā)起人)為例,無(wú)論什么時(shí)候你執(zhí)行一些計(jì)算哼审,最后的計(jì)算都會(huì)保存在內(nèi)存(即備忘)里谐腰,這樣你就能返回到這里,并且用一些按鈕(即守護(hù)者)恢復(fù)涩盾。

白話

備忘錄模式捕捉和保存當(dāng)前對(duì)象的狀態(tài)十气,然后用一種平滑的方式恢復(fù)。

維基百科

The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).

當(dāng)你要提供撤銷方法時(shí)異常實(shí)用春霍。

代碼例子

讓我們那編輯器為例砸西,編輯器一直保存狀態(tài),在你需要的時(shí)候可以恢復(fù)址儒。

首先下面是我們的備忘錄對(duì)象芹枷,可以保存編輯器狀態(tài)

class EditorMemento {
    protected $content;
    
    public function __construct(string $content) {
        $this->content = $content;
    }
    
    public function getContent() {
        return $this->content;
    }
}

然后是我們的編輯器,即發(fā)起者离福,來(lái)使用備忘錄對(duì)象

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.');

// 保存狀態(tài)到: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.

// 恢復(fù)到上次保存狀態(tài)
$editor->restore($saved);

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

?? 觀察者模式

現(xiàn)實(shí)例子

一個(gè)好的例子是求職者杖狼,他們訂閱了一些工作發(fā)布網(wǎng)站,當(dāng)有合適的工作機(jī)會(huì)時(shí)妖爷,他們會(huì)收到提醒蝶涩。

白話

定義了一個(gè)對(duì)象間的依賴理朋,這樣無(wú)論何時(shí)一個(gè)對(duì)象改變了狀態(tài),其他所有依賴者會(huì)收到提醒绿聘。

維基百科

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.

代碼例子

翻譯上面的例子嗽上。首先我們有需要收到工作發(fā)布提醒的求職者

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

然后可以這樣使用

// 創(chuàng)建訂閱者
$johnDoe = new JobSeeker('John Doe');
$janeDoe = new JobSeeker('Jane Doe');
$kaneDoe = new JobSeeker('Kane Doe');

// 創(chuàng)建發(fā)布者,綁定訂閱者
$jobPostings = new JobPostings();
$jobPostings->attach($johnDoe);
$jobPostings->attach($janeDoe);

// 添加一個(gè)工作熄攘,看訂閱者是否收到通知
$jobPostings->addJob(new JobPost('Software Engineer'));

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

?? 訪問(wèn)者模式

現(xiàn)實(shí)例子

假設(shè)一些人訪問(wèn)迪拜兽愤。他們需要一些方式(即簽證)來(lái)進(jìn)入迪拜。抵達(dá)后挪圾,他們可以去迪拜的任何地方浅萧,而不用申請(qǐng)?jiān)S可或者跑腿;他們知道的地方都可以去哲思。訪問(wèn)者模式可以讓你這樣做洼畅,它幫你添加可以訪問(wèn)的地方,然后他們可以訪問(wèn)盡可能多的地方而不用到處跑腿棚赔。

白話

訪問(wèn)者模式可以讓你添加更多的操作到對(duì)象帝簇,而不用改變他們。

維基百科

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.

代碼例子

讓我們以動(dòng)物園模擬器為例靠益,在里面我們有一些動(dòng)物丧肴,我們必須讓他們叫。讓我們用訪問(wèn)者模式來(lái)翻譯

// 被訪者
interface Animal {
    public function accept(AnimalOperation $operation);
}

// 訪問(wèn)者
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);
    }
}

實(shí)現(xiàn)我們的訪問(wèn)者

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!

我們本可以簡(jiǎn)單地給動(dòng)物加一個(gè)繼承層來(lái)做到這點(diǎn)胧后,但是這樣每當(dāng)我們要給動(dòng)物增加新功能的時(shí)候芋浮,我們就不得不改變動(dòng)物。但是現(xiàn)在我們不用改變他們绩卤。比如途样,我們要給動(dòng)物增加一個(gè)跳的行為,我們可以通過(guò)簡(jiǎn)單地增加一個(gè)新的訪問(wèn)者

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

?? 策略模式

現(xiàn)實(shí)例子

考慮排序的例子濒憋,我們實(shí)現(xiàn)了冒泡排序何暇,但是數(shù)據(jù)開(kāi)始增長(zhǎng),冒泡排序變得很慢凛驮。為了應(yīng)對(duì)這個(gè)裆站,我們實(shí)現(xiàn)了快速排序。但現(xiàn)在盡管快速排序算法對(duì)大數(shù)據(jù)集表現(xiàn)更好黔夭,小數(shù)據(jù)集卻很慢宏胯。為了應(yīng)對(duì)這一點(diǎn),我們實(shí)現(xiàn)一個(gè)策略本姥,冒泡排序處理小數(shù)據(jù)集肩袍,快速排序處理大數(shù)據(jù)集。

白話

策略模式允許你基于情況選擇算法或策略婚惫。

維基百科

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.

代碼例子

翻譯我們上面的例子氛赐。首先我們有了策略接口和不同的策略實(shí)現(xiàn)

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

然后是實(shí)用策略的調(diào)用者

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

?? 狀態(tài)模式

現(xiàn)實(shí)例子

想象你在使用畫(huà)圖程序魂爪,你選擇筆刷來(lái)畫(huà)。現(xiàn)在筆刷根據(jù)選擇的顏色改變自己的行為艰管。即如果你選擇紅色滓侍,它就用紅色畫(huà),如果是藍(lán)色它就用藍(lán)色等等牲芋。

白話

他讓你能類的狀態(tài)改變時(shí)撩笆,改變其行為。

維基百科

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.

代碼例子

讓我們以編輯器作為例子缸浦,它能讓你改變文本的狀態(tài)夕冲,比如你選擇了加粗,它開(kāi)始以加粗字體書(shū)寫(xiě)餐济,如果選擇傾斜耘擂,就以傾斜字體等等。

首先絮姆,我們有狀態(tài)接口和一些狀態(tài)實(shí)現(xiàn)

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

?? 模板模式

現(xiàn)實(shí)例子

假設(shè)我們要建房子。建造的步驟類似這樣

  • 準(zhǔn)備房子的地基
  • 建造墻
  • 建造房頂
  • 然后是地板
    這些步驟步驟的順序永遠(yuǎn)不會(huì)變秩霍,即你不能在建墻之前建屋頂篙悯,當(dāng)時(shí)每個(gè)步驟都可以改變,比如墻可以是木頭可以是聚酯或者石頭铃绒。

白話

模板模式定義了一個(gè)算法會(huì)如何執(zhí)行的骨架鸽照,但把這些步驟的實(shí)現(xiàn)移交給子類。

維基百科

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.

代碼例子

想象我們有一個(gè)構(gòu)建工具幫我們測(cè)試颠悬,糾錯(cuò)矮燎,構(gòu)建,生成構(gòu)建報(bào)告(即代碼報(bào)告赔癌,查錯(cuò)報(bào)告)诞外,然后把應(yīng)用發(fā)布到測(cè)試服務(wù)器。

首先是我們的基礎(chǔ)類灾票,它描述了構(gòu)建算法的骨架

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

以下是實(shí)現(xiàn)

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

?? 收尾了同志們

終于收尾了峡谊。我會(huì)繼續(xù)改進(jìn)這篇文檔,所以你或許需要 watch/star 這個(gè)倉(cāng)庫(kù)刊苍,先碼后看既们。

?? Contribution

  • Report issues
  • Open pull request with improvements
  • Spread the word

翻譯

月球人

License

MIT ? Kamran Ahmed

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市正什,隨后出現(xiàn)的幾起案子啥纸,更是在濱河造成了極大的恐慌,老刑警劉巖婴氮,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斯棒,死亡現(xiàn)場(chǎng)離奇詭異馒索,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)名船,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)绰上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人渠驼,你說(shuō)我怎么就攤上這事刘陶。” “怎么了烙博?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵存和,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蜓席,道長(zhǎng)器一,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任厨内,我火速辦了婚禮祈秕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雏胃。我一直安慰自己请毛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布瞭亮。 她就那樣靜靜地躺著方仿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪统翩。 梳的紋絲不亂的頭發(fā)上仙蚜,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音厂汗,去河邊找鬼委粉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛面徽,可吹牛的內(nèi)容都是我干的艳丛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼趟紊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼氮双!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起霎匈,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戴差,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后铛嘱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體暖释,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袭厂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了球匕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纹磺。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亮曹,靈堂內(nèi)的尸體忽然破棺而出橄杨,到底是詐尸還是另有隱情,我是刑警寧澤照卦,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布式矫,位于F島的核電站,受9級(jí)特大地震影響役耕,放射性物質(zhì)發(fā)生泄漏采转。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一瞬痘、第九天 我趴在偏房一處隱蔽的房頂上張望故慈。 院中可真熱鬧,春花似錦图云、人聲如沸惯悠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筒严,卻和暖如春丹泉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸭蛙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工摹恨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娶视。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓晒哄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親肪获。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寝凌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容