設計模式在PHP業務場景中的應用

1.設計模式概述

設計模式是一把雙刃劍,在工做場景中,只有不多的設計模式會被在業務場景中使用到,最經常使用的就是單例模式。相反,工做中使用的框架則在底層使用到了大量的設計模式,這樣作提升了框架的性能、更好的解耦各個模塊之間的功能、提供更好的拓展性、最大程度簡化了開發者使用成本。實際上,在業務場景中使用合適的設計模式,也會達到事半功倍的效果。不過要想使用好設計模式,必需要深入理解它設計的初衷和適用的場景分別是什麼,這並非一件容易的事情!本文結合四種常見的設計模式,聊一聊在php應用開發場景中,怎樣更好的使用設計模式來簡化業務邏輯,使得需求可拓展,代碼更容易維護。本文使用到的案例放在:設計模式 ,先來學習一下基本知識:設計模式的七大原則、分類和UML類圖的使用。php

1.1 設計模式的七大原則

  • 開閉原則( Open Close Principle );在對程序進行更新迭代的過程當中,應當合理的避免修改類或方法的內部代碼,而是優先選擇經過繼承、擴展等方式來實現。簡而言之,就是:對擴展開放,對修改關閉。
  • 里氏替換原則( Liskov Substitution Principle );在實現子類的定義時,應該讓它徹底擁有替代父類進行工做的能力。簡而言之,就是:子類對外要具與父類一致的方法或接口。
  • 依賴倒置原則( Dependence Inversion Principle );在對象或類的依賴關係定義上,父類或者其餘上層實現不該該依賴於子類或者其餘下層實現,經過這樣,來避免依賴關係的耦合。
  • 單一職責原則( Single Responsibility Principle );在程序結構和依賴關係的定義上,要將類的功能職責充分理清,盡力減小類之間的耦合。避免對某個類進行修改時,牽一髮動全身的連鎖反應。
  • 接口隔離原則( Interface Segregation Principle );在對外接口的定義上,要避免龐大而臃腫的接口,而是進行責任細化的區分,避免冗餘的代碼實現。這對於提升內聚,提高系統靈活度是很是有效果的。
  • 最少知識原則( Least Knowledge Principle );在分配類的職責和創建依賴關係時,應該只關注於自身的功能實現和周圍與之接觸類的交互方式。避免類去考慮整個系統結構和處理流程,讓類的職責清晰化,讓系統的耦合度下降。
  • 合成複用原則( Composite Reuse Principle );在擴展功能的時候,要優先考慮水平形式的新增類或方法,而不是經過繼承去實現。也就是經過功能的組合實現類,而不是經過基礎去實現新的功能。這樣能夠提升類的可擴展性,減小系統的層次。

這七大原則爲咱們設計程序提供了指導,能夠說是優秀程序設計的方法論。 不過理論每每又是簡短而抽象的,你們想要理解並熟練用它們去指導程序設計,還需從大量的實踐中去領悟。下邊咱們介紹使用設計模式的時候也會根據這七大原則設計。html

1.2 設計模式的分類

設計模式自己是很是豐富的,通常將面向對象的設計模式分爲三類:建立型、結構型和結構型。mysql

1.2.1 建立型git

建立對象時,再也不由咱們直接實例化對象;而是根據特定場景,由程序來肯定建立對象的方式,從而保證更大的性能、更好的架構優點。 建立型模式主要有:程序員

  • 單例模式(經常使用)
  • 工廠模式:簡單工廠模式、工廠方法模式、抽象工廠模式(經常使用)(注:簡- 單工廠模式不屬於23種設計模式)
  • 生成器模式
  • 原型模式

1.2.2 結構型github

用於幫助將多個對象組織成更大的結構。面試

結構型模式主要有:sql

  • 適配器模式(經常使用)
  • 裝飾器模式
  • 代理模式
  • 門面模式(外觀模式)
  • 橋接模式
  • 組合模式
  • 亨元模式

1.2.3 行爲型數據庫

用於幫助系統間各對象的通訊,以及如何控制複雜系統中流程。設計模式

行爲型模式主要有:

  • 策略模式(經常使用)
  • 觀察者模式(經常使用)
  • 模板模式
  • 迭代器模式
  • 職責鏈模式
  • 命令模式
  • 備忘錄模式
  • 狀態模式
  • 訪問者模式
  • 中介者模式
  • 解釋器模式

1.3 UML類圖的使用

不少東西使用文字表述是蒼白無力的,尤爲是設計模式這種抽象的理論。咱們使用UML類圖來增長咱們的表達力。類圖(Class diagram)主要用於描述系統的結構化設計。類圖也是最經常使用的UML圖,用類圖能夠顯示出類、接口以及它們之間的靜態結構和關係。

在UML類圖中,常見的有如下幾種關係:

  • 繼承/泛化(Generalization):用於描述父類與子類之間的關係。父類又稱做基類,子類又稱做派生類。經過extends實現,如:class Bus extends Car
  • 實現(Realization),主要用來規定接口和實現類的關係。經過implements實現,如:class Car implements Vehicle
  • 關聯(Association)
  • 聚合(Aggregation)
  • 組合(Composition)
  • 依賴(Dependency)

這六種類關係中,組合、聚合和關聯的代碼結構同樣,能夠從關係的強弱來理解,各種關係從強到弱依次是:繼承→實現→組合→聚合→關聯→依賴。(目前本身的理解)除了繼承和實現,其餘類關係較弱,通常是經過在一個類中調用其餘類來實現。

2.四種設計模式在業務場景中的應用

咱們本節介紹的四種設計模式分別是:工廠模式、裝飾器模式、發佈/訂閱模式、迭代器模式。選擇這四種設計模式是由於在PHP業務場景中會常常用到他們,筆者還整理了一些相關的一些案例。

2.1 工廠模式

「工廠模式」簡單來說就是將建立對象的任務交給工廠,根據抽象層次的不一樣,又分爲:簡單工廠、工廠方法和抽象工廠。

簡單工廠模式(Simple Factory Pattern):又稱爲靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式。在簡單工廠模式中,能夠根據參數的不一樣返回不一樣類的實例。簡單工廠模式專門定義一個類來負責建立其餘類的實例,被建立的實例一般都具備共同的父類。它的抽象層次低,工廠類通常也不會包含複雜的對象生成邏輯,只能適用於生成結構比較簡單,擴展性要求較低的對象。

工廠方法模式(Factory Method Pattern): 又稱爲工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,它屬於類建立型模式。在工廠方法模式中,工廠父類負責定義建立產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣作的目的是將產品類的實例化操做延遲到工廠子類中完成,即經過工廠子類來肯定究竟應該實例化哪個具體產品類。

抽象工廠模式(Abstract Factory Pattern) :提供一個建立一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱爲Kit模式,屬於對象建立型模式。抽象工廠模式包含以下角色:

  • AbstractFactory:抽象工廠
  • ConcreteFactory:具體工廠
  • AbstractProduct:抽象產品
  • Product:具體產品

咱們使用工廠模式的業務場景是這樣的:一個專門作「運動戶外」新零售的公司,想要在本身的APP上向用戶推薦不一樣的服裝、鞋帽搭配套裝,推薦的策略根據用戶性別的不一樣有所不一樣,男生推薦鞋子和揹包,女生推薦外套和褲子;根據產品策略的不一樣,又有不一樣的推薦版本,好比第一個版本只推薦阿迪達斯的產品,第二個版本則推薦耐克的產品。這個業務場景就很是適合使用「抽象工廠模式」。咱們先來看一下UML類圖:

工廠類UML類圖實現

是的,使用抽象工廠模式確實有一個缺點:類特別多。可是帶來的好處也是很明顯的,咱們先來看一下代碼實現:

首先是抽象工廠類:

abstract class recommendFactory
{
    public function createRecommendClass($sex) {}
}
複製代碼

咱們假設每個版本的推薦都有一個相關的工廠類,他們都繼承抽象工廠類:

class concreteRecommendFactoryV1 extends recommendFactory
{
    public function createRecommendClass($sex) {
        switch ($sex) {
            case 'man':
                return new concreteRecommendClassV1man();
            case 'women':
                return new concreteRecommandClassV1women();
        }
    }
}
......
class concreteRecommendFactoryV2 extends recommendFactory
{
    public function createRecommendClass($sex) {
        switch ($sex) {
            case 'man':
                return new concreteRecommendClassV2man();
            case 'women':
                return new concreteRecommendClassV2women();
        }
    }
}
複製代碼

工廠類用來生產產品對象,根據用戶的性別生產不一樣的對象實例,產品對象也都有繼承的抽象類,咱們來看一下產品的抽象類:

abstract class recommendClass
{
    public function recommendRun() {}
}
複製代碼

而後工廠類生產出來根據產品類獲得的產品實例。咱們來分別看一下產品類的內容:

class concreteRecommendClassV1man extends recommendClass
{
    public function recommendRun() {
        return '推薦耐克鞋子和揹包';
    }
}
......
class concreteRecommandClassV1women extends recommendClass
{
    public function recommendRun() {
        return '推薦耐克上衣和褲子';
    }
}
......
class concreteRecommendClassV2man extends recommendClass
{
    public function recommendRun() {
        return '推薦阿迪達斯鞋子和揹包';
    }
}
......
class concreteRecommendClassV2women extends recommendClass
{
    public function recommendRun() {
        return '推薦阿迪達斯上衣和褲子';
    }
}
複製代碼

這樣咱們就能夠根據不一樣的版本,用戶不一樣的性別來展現不一樣的推薦信息了:

include './recommendClass.php';
include './concreteRecommendClassV1man.php';
include './concreteRecommendClassV2man.php';
include './concreteRecommandClassV1women.php';
include './concreteRecommendClassV2women.php';
include './recommendFactory.php';
include './concreteRecommendFactoryV1.php';
include './concreteRecommendFactoryV2.php';

$sex = $_GET['sex'];
$version = $_GET['version'];

switch ($version) {
    case 'version1' :
        $factory = new concreteRecommendFactoryV1();
        break;
    case 'version2' :
        $factory = new concreteRecommendFactoryV2();
}

$recommendClass = $factory->createRecommendClass($sex);
$recommendContent = $recommendClass->recommendRun();
echo $recommendContent.'<br/>';
複製代碼

雖然咱們實現邏輯中用到的類比較的多,可是代碼的可拓展性很強。再次發佈不一樣的版本推薦策略時,咱們只要建立相應的工廠,生產對應的類便可。

抽象工廠模式隔離了具體類的生成,因爲這種隔離,更換一個具體工廠就變得相對容易。全部的具體工廠都實現了抽象工廠中定義的那些公共接口,所以只需改變具體工廠的實例,就能夠在某種程度上改變整個軟件系統的行爲。另外,應用抽象工廠模式能夠實現高內聚低耦合的設計目的,所以抽象工廠模式獲得了普遍的應用。

2.2 裝飾器模式

裝飾器模式屬於結構型模式,裝飾器模式能夠動態的給一個對象添加額外的功能,就增長功能來講,裝飾器模式比生成子類更爲靈活;它容許向一個現有的對象添加新的功能,同時又不改變其結構。讀過Laravel源碼的人應該都知道,Laravel中間件(Middleware)的實現就是使用的裝飾器模式;Koa.js 最爲人所知的基於 洋蔥模型 的HTTP中間件處理流程也是裝飾器模式。關於Laravel中間件源碼的實現,筆者專門整理了一篇博文,感興趣的讀者能夠讀一下:Lumen中間件源碼解析

咱們如今有這樣一個需求:如今有一家餐館,入住美團以後提供外賣服務,經過讓顧客選套餐的方式,提供給用戶選擇的多樣性,以此來增長銷量。其中套餐能夠由:主食、素菜、飲料、葷菜組成,其中主食和素材是基本套餐項,顧客能夠再次基礎上再選擇添加飲料和葷菜,簡單列一下菜系:

  • 主食:米飯、饅頭
  • 素菜:土豆絲、番茄雞蛋、炒豆角
  • 飲料:雪碧、可樂、酸梅湯
  • 葷菜:回鍋肉、牛排、羊排

明確了需求,咱們來看一下案例中使用到裝飾器所用到的UML類圖:

裝飾器模式UML類圖

根據UML類圖,咱們來看一下代碼實現,首先是添加菜品的接口:

<?php
/**
 * Description: 抽象接口,真實對象和裝飾對象具備相同的接口
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */
interface AbstractComponent{
    public function addCategory(array &$dishes, Closure $next);
}
複製代碼

一個具體要裝飾的對象ConcreteComponent實現了這個接口:

<?php
/**
 * Description: 具體的對象
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class ConcreteComponent implements AbstractComponent
{
    public function addCategory(array &$dishes, Closure $next)
    {
        return function(&$dishes) use ($next) {
            $dishes[] = ['米飯', '饅頭'];
            $dishes[] = ['土豆絲', '番茄雞蛋', '炒豆角'];

            return $next($dishes);
        };
    }
}
複製代碼

咱們這裏直接定義了主食和素材做爲基礎套餐,接下來對它進行裝飾。 接下來就是定義了裝飾器的抽象類了:

<?php
/**
 * Description: 裝飾類,繼承了Component,從外類來擴展Component類的功能。
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

abstract class AbstractDecorator implements AbstractComponent
{
    private function initCategory() {}
    public function addCategory(array &$dishes, Closure $next) {}
}
複製代碼

抽象類中定義了一個私有方法initCategory,用來管理菜系的菜品,好比說素菜有:土豆絲、番茄雞蛋、炒豆角,未來還可能添加豆腐、海帶等其餘素菜菜品。addCategory就是裝飾器,將菜品添加到用戶選擇的菜系中。葷菜類和飲料類菜品繼承了抽象裝飾器:

<?php
/**
 * Description: 葷菜裝飾器,爲菜品添加葷菜
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class ChivesDecorator extends AbstractDecorator
{
    private function initCategory()
    {
        return ['回鍋肉', '牛排', '羊排'];
    }

    public function addCategory(array &$dishes, Closure $next)
    {
        $dishes[] = $this->initCategory();
        return $next($dishes);
    }
}
......
/**
 * Description: 飲料裝飾器,爲菜品添加類
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class DrinkDecorator
{
    private function initCategory()
    {
        return ['雪碧', '可樂', '酸梅湯'];
    }

    public function addCategory(array &$dishes,Closure $next)
    {
        $dishes[] = $this->initCategory();
        return $next($dishes);
    }
}
複製代碼

基本的類都實現了以後,咱們來看一下怎樣在基礎套餐之上進行裝飾吧:

<?php
include __DIR__.'/AbstractComponent.php';
include __DIR__.'/AbstractDecorator.php';
include __DIR__.'/ConcreteComponent.php';
include __DIR__.'/DrinkDecorator.php';
include __DIR__.'/ChivesDecorator.php';

//將用戶選好的套餐排列組合
$output = function ($dishes) {
    $items = [];
    $init = array_shift($dishes);
    foreach($init as $item) {
        $items[] = [$item];
    }
    do{
        $elements = array_shift($dishes);
        $temp = [];
        foreach($elements as $element) {
            foreach($items as $item) {
                $item[] = $element;
                $temp[] = $item;
            }
        }
        $items = $temp;
    } while(count($dishes));

    return $items;
};

//組合函數,將裝飾器函數一層層包裝
function combinationFunc()
{
    return function($stack, $decorators){
        return function (&$dishes) use ($stack, $decorators) {
            return $decorators->addCategory($dishes, $stack);
        };
    };
}

$dishes = [];
$baseCategory = (new ConcreteComponent())->addCategory($dishes, $output);

print '加葷菜和飲料後的套餐組合<br/>';
$addCategoryDecorators = [new DrinkDecorator(), new ChivesDecorator()];
$go = array_reduce(array_reverse($addCategoryDecorators), combinationFunc(), $baseCategory);

var_dump($go($dishes));
複製代碼

首先咱們將用戶選擇的套餐放到數組$dishes中,咱們向套餐中添加基礎套餐 $baseCategory = (new ConcreteComponent())->addCategory($dishes, $output);

其中$output是排列組合函數,用戶選擇套餐以後,對用戶選擇的菜品進行排列組合,好比用戶選擇了主食、素材和葷菜,那麼咱們就從這三種菜系中各挑出一個菜進行組合造成套餐。提及來容易,可是對二維數組排列組合不是一件簡單的事情,筆者這裏的方法讀者能夠參考一下:

$output = function ($dishes) {
    $items = [];
    $init = array_shift($dishes);
    foreach($init as $item) {
        $items[] = [$item];
    }
    do{
        $elements = array_shift($dishes);
        $temp = [];
        foreach($elements as $element) {
            foreach($items as $item) {
                $item[] = $element;
                $temp[] = $item;
            }
        }
        $items = $temp;
    } while(count($dishes));

    return $items;
};
複製代碼

接下來咱們用葷菜類和飲料類對基本的套餐類進行裝飾:

print '加葷菜和飲料後的套餐組合<br/>';
$addCategoryDecorators = [new DrinkDecorator(), new ChivesDecorator()];
$go = array_reduce(array_reverse($addCategoryDecorators), combinationFunc(), $baseCategory);

var_dump($go($dishes));
複製代碼

這樣就達到了咱們想要的效果,若是讀者對array_reduce函數不是很理解的話能夠看一下官方文檔。這樣一個選擇套餐的裝飾器就完成了,咱們能夠根據用戶選擇的套餐信息來組合裝飾器,好比這裏的$addCategoryDecorators是飲料類和葷菜類,未來還能夠增長其餘用戶選擇的菜系。這種裝飾器模式並無經過繼承來拓展基礎類套餐,而是經過組合橫向拓展了類的能力,後期代碼也方便維護,裝飾器模式也能夠稱爲責任鏈模式、管道模式,在項目中也常常會使用到。

2.3 發佈/訂閱模式

發佈/訂閱模式(又稱爲觀察者模式,屬於行爲型模式的一種,它是將行爲獨立模塊化,下降了行爲和主體的耦合性。它定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態變化時,會通知全部的觀察者對象,使他們可以自動更新本身。熟悉js操做DOM的讀者應該知道,爲DOM元素綁定一個事件可使用addEventListener方法,其實這就是一種發佈/訂閱模式,當事件發生時,各個監聽的組建就會收到通知,繼而產生交互效果。發佈/訂閱模式在項目中使用的很是多,例如Lumen框架中想要監聽數據庫的操做,並把數據庫的每一次操做都記錄到日誌當中去,未來能夠分析慢查詢問題,就可使用以下方法:

\DB::listen(function ($query) {
               ....//這裏是對sql語句的操做
            });
複製代碼

php底層的不少實現機制也都使用到了發佈/訂閱模式,好比信號處理、錯誤處理等。和生產者、消費者模式不一樣的是,生產者、消費者每每是在兩個進程中進行的,是一種異步處理的策略,發佈/訂閱模式則是當主體對象發生改變時,實時通知到觀察者。

發佈/訂閱模式如此重要,以致於SPL直接給出了實現方案,咱們本例中也是經過SPL提供的接口SplSubject/SplObserver來實現的,例子也是最普通的:學校中當有新書上架時,將新書上架的消息通知給老師和學生。

咱們先來看一下發布/訂閱模式的UML類圖:

訂閱發佈模式UML類圖

其中SplSubject類提供添加監聽者(attach),刪除監聽者(detach),通知監聽者(notify)三個方法,Book類實現了這個接口,咱們來看一下代碼:

<?php
/**
* Description: 訂閱與發佈模式的實現
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Book implements SplSubject
{
   private $author = null;    //做者
   private $price = null;     //售價
   private $name = null;      //書名

   private $observers = null;

   public function __construct($name, $author, $price)
   {
       $this->name = $name;
       $this->author = $author;
       $this->price = $price;

       //初始化觀察者爲spl對象存儲
       $this->observers = new SplObjectStorage();
   }

   /**
    * 添加觀察者
    * @param SplObserver $observer
    */
   public function attach(SplObserver $observer)
   {
       $this->observers->attach($observer);
   }

   /**
    * 刪除觀察者
    * @param SplObserver $observer
    */
   public function detach(SplObserver $observer)
   {
       $this->observers->detach($observer);
   }

   /**
    * 通知觀察者
    */
   public function notify()
   {
       $params = [
           'name' => $this->name,
           'author' => $this->author,
           'price' => $this->price
       ];

       foreach ($this->observers as $observer) {
           $observer->update($this, $params);
       }
   }
}
複製代碼

咱們這裏是用到了SplObjectStorage對象來存儲觀察者對象,這樣就省去了底層數組的不少操做細節,好比in_array判斷觀察者對象是否已經存在了,這是一種委託設計模式。接下來咱們再來實現訂閱者,訂閱者只須要實現SplObserver的update方法便可:

<?php
/**
* Description: 學生類觀察者
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Student implements SplObserver
{
   public function update(\Splsubject $subject)
   {
       // TODO: Implement update() method.
       if(func_num_args() == 2) {
           $params = func_get_arg(1);
           echo '學生已經收到',$params['name'],'上架的信息','做者:',$params['author'],'訂價:',$params['price'],"<br>";
       }
   }
}
......
class Teacher implements SplObserver
{
   public function update(SplSubject $subject)
   {
       // TODO: Implement update() method.
       if(func_num_args() == 2) {
           $params = func_get_arg(1);
           echo '老師已經收到',$params['name'],'上架的信息','做者:',$params['author'],'訂價:',$params['price'],"<br/>";
       }
   }
}
複製代碼

學生類和老師類收到新書上架的信息後,會打印出接收通知,update接收一個SplSubject對象,其實在Observer類中接收Subject對象屬性的方法更好的是在Subject對象中添加一個getParams的方法,不直接去訪問對象的內部屬性,這樣作設計模式中開閉原則。本例中經過參數接收發布者傳遞過來的信息,有些取巧,不過也達到了效果。接下來只要給發佈者添加監聽對象就能夠了:

<?php
include __DIR__.'/Book.php';
include __DIR__.'/Student.php';
include __DIR__.'/Teacher.php';

$student = new Student();
$teacher = new Teacher();
$book = new Book('<<鋼鐵是怎樣煉成的>>', '奧斯特洛夫斯基', '79.00');
$book->attach($student);
$book->attach($teacher);

$book->notify();
複製代碼

幾乎全部的api框架中都會提供這樣一種事件發佈/訂閱機制,Laravel中專門有本身的Event模塊的設計,基於此能夠實現事件的廣播。發佈/訂閱模式實現相對簡單,讀者本身也能夠進行實現。

2.4 迭代器模式

迭代器模式(Iterator),又叫作遊標(Cursor)模式。提供一種方法訪問一個容器(Container)對象中各個元素,而又不需暴露該對象的內部細節。 當你須要訪問一個聚合對象,並且無論這些對象是什麼都須要遍歷的時候,就應該考慮使用迭代器模式。另外,當須要對彙集有多種方式遍歷時,能夠考慮去使用迭代器模式。迭代器模式爲遍歷不一樣的彙集結構提供如開始、下一個、是否結束、當前哪一項等統一的接口。 PHP標準庫(SPL)中提供了迭代器接口 Iterator,要實現迭代器模式,實現該接口便可。咱們來看一個簡單的例子,咱們使用對象從數據庫中取到數據以後,想要遍歷取出對象中的數據,就可使用迭代器模式,UML類圖很是簡單:

咱們先來實現一個簡單的數據庫鏈接查詢類,php7之後移除了mysqli模塊,推薦使用PDO操做數據庫(筆者認爲這也和設計模式有關係,php程序員已經習慣了提到php就想到mysql,而PDO卻能夠將操做mysql、sqlserver、Oracle其餘數據庫的操做封住成統一的接口,這是一種適配器的設計模式)。咱們例子使用PDO簡單實現一下:

class PdoDB
{
   private static $conn = null;

   //禁止被實例化
   private function __construct()
   {
   }

   private static function connDB()
   {
       return new PDO('mysql:host=localhost;sort=3306;dbname=study;', 'root', 'root');
   }

   /**
    * 獲取數據庫鏈接單例
    * @return null|PDO
    */
   public static function getInstance()
   {
       if (is_null(self::$conn)) {
           self::$conn = self::connDB();
       }
       return self::$conn;
   }

   private function __clone()
   {
       // TODO: Implement __clone() method.
       echo 'error!';
   }
}
複製代碼

咱們那的PdoDB使用了單例模式實現,單例模式中的構造函數是private,另外當用戶對對象進行克隆的時候,應該報錯(克隆對象的操做也是一種設計模式,叫原型模式,和單例模式不一樣的是:原型模式是建立型模式,是建立新對象,而單例模式的目的是共用一個對象)。接下來咱們實現Users類,其中使用PdoDB操做數據庫取出數據,並實現迭代器Iterator:

<?php
/**
* Description: 迭代類
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Users implements Iterator
{
   protected $data;
   protected $index;

   public function __construct()
   {
       $this->data = PdoDB::getInstance()->query('select * from users')->fetchAll();
   }

   public function current()
   {
       $current = $this->data[$this->index];

       return $current;
   }

   public function next()
   {
       $this->index++;
   }

   public function key()
   {
       return $this->index;
   }

   public function valid()
   {
       return $this->index < count($this->data);
   }

   public function rewind()
   {
       $this->index = 0;
   }
}
複製代碼

而後咱們就能夠對Users對象進行遍歷了:

<?php
/**
 * Description: File basic description here...
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-27
 */
include __DIR__.'/PdoDB.php';
include __DIR__.'/Users.php';

//對數據進行迭代
$users = new Users();
foreach($users as $user) {
    var_dump($user);
}
複製代碼

上邊的例子很簡單;說到迭代器,不得不提一下PHP另一個強大的特性:生成器,生成器是 PHP 5.5 引入的新特性,可是目前貌似不多人用到它。 下面試 PHP 官方文檔上對生成器的解釋: 生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 接口的方式,性能開銷和複雜性大大下降。 生成器容許你在 foreach 代碼塊中寫代碼來迭代一組數據而不須要在內存中建立一個數組, 那會使你的內存達到上限,或者會佔據可觀的處理時間。相反,你能夠寫一個生成器函數,就像一個普通的自定義函數同樣, 和普通函數只返回一次不一樣的是, 生成器能夠根據須要 yield 屢次,以便生成須要迭代的值。

咱們來看一個簡單的文件讀取的例子:

<?php
/**
 * Description: 生成器讀取超大文件
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-27
 */
header("content-type:text/html;charset=utf-8");

/*$startMemory = memory_get_usage();
$value = file_get_contents('./test.txt');
$useMemory = memory_get_usage() - $startMemory;
echo '一共佔用了',$useMemory,'字節內存';*/

function readTxt()
{
    $handle = fopen('./test.txt', 'rb');

    while (feof($handle)===false) {
        yield fgets($handle);
    }

    fclose($handle);
}

$startMemory = memory_get_usage();
foreach (readTxt() as $key => $value) {
   $lineData = $value;
}

$useMemory = memory_get_usage() - $startMemory;
echo '一共佔用了',$useMemory,'字節內存';
複製代碼

運行上邊案例,對比使用file_get_contents讀取文件,咱們看到使用生成器節省了大量的內存,生成器的引入使得程序中的函數達到了中斷的效果,實現迭代器也只須要使用yield關鍵字便可,yield返回的就是一個Iterator對象。總的來講,迭代器模式在業務場景中不經常使用,可是頗有用,讀者若是以前沒接觸過相似的概念,能夠蒐集資料學習一下並在項目中使用。

3.小結

設計模式的內容多多少少仍是有些抽象的,它是一把雙刃劍,很容易被濫用。不一樣的設計模式適用於不一樣的業務場景,只有經過大量的經驗積累和學習理解,才能將設計模式用的恰到好處。設計模式的最終目的是爲了使系統解耦,方便開發者維護代碼。設計模式有不少,筆者認爲,結合不一樣的業務場景嘗試使用合理的設計模式解決問題,是最好的學習方式。

相關文章
相關標籤/搜索