1. 程式人生 > >23種設計模式全解析及五種常見的 PHP 設計模式

23種設計模式全解析及五種常見的 PHP 設計模式

一、設計模式的分類

總體來說設計模式分為三大類:

建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、直譯器模式。

二、設計模式的六大原則

總原則:開閉原則(Open Close Principle)

開閉原則就是說對擴充套件開放,對修改關閉。在程式需要進行拓展的時候,不能去修改原有的程式碼,而是要擴充套件原有程式碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程式的擴充套件性好,易於維護和升級。想要達到這樣的效果,我們需要使用介面和抽象類等,後面的具體設計中我們會提到這點。

1、單一職責原則

不要存在多於一個導致類變更的原因,也就是說每個類應該實現單一的職責,如若不然,就應該把類拆分。

2、里氏替換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向物件設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟體單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。—— From Baidu 百科

歷史替換原則中,子類對父類的方法儘量不要重寫和過載。因為父類代表了定義好的結構,通過這個規範的介面與外界互動,子類不應該隨便破壞它。

3、依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:面向介面程式設計,依賴於抽象而不依賴於具體。寫程式碼時用到具體類時,不與具體類互動,而與具體類的上層介面互動。

4、介面隔離原則(Interface Segregation Principle)

這個原則的意思是:每個介面中不存在子類用不到卻必須實現的方法,如果不然,就要將介面拆分。使用多個隔離的介面,比使用單個介面(多個介面方法集合到一個的介面)要好。

5、迪米特法則(最少知道原則)(Demeter Principle)

就是說:一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。

最少知道原則的另一個表達方式是:只與直接的朋友通訊。類之間只要有耦合關係,就叫朋友關係。耦合分為依賴、關聯、聚合、組合等。我們稱出現為成員變數、方法引數、方法返回值中的類為直接朋友。區域性變數、臨時變數則不是直接的朋友。我們要求陌生的類不要作為區域性變量出現在類中。

6、合成複用原則(Composite Reuse Principle)

原則是儘量首先使用合成/聚合的方式,而不是使用繼承。

五種常見的 PHP 設計模式

  

工廠模式

最初在設計模式 一書中,許多設計模式都鼓勵使用鬆散耦合。要理解這個概念,讓我們最好談一下許多開發人員從事大型系統的艱苦歷程。在更改一個程式碼片段時,就會發生問題,系統其他部分 —— 您曾認為完全不相關的部分中也有可能出現級聯破壞。

該問題在於緊密耦合 。系統某個部分中的函式和類嚴重依賴於系統的其他部分中函式和類的行為和結構。您需要一組模式,使這些類能夠相互通訊,但不希望將它們緊密繫結在一起,以避免出現聯鎖。

在大型系統中,許多程式碼依賴於少數幾個關鍵類。需要更改這些類時,可能會出現困難。例如,假設您有一個從檔案讀取的 User 類。您希望將其更改為從資料庫讀取的其他類,但是,所有的程式碼都引用從檔案讀取的原始類。這時候,使用工廠模式會很方便。

工廠模式 是一種類,它具有為您建立物件的某些方法。您可以使用工廠類建立物件,而不直接使用 new。這樣,如果您想要更改所建立的物件型別,只需更改該工廠即可。使用該工廠的所有程式碼會自動更改。

清單 1 顯示工廠類的一個示列。等式的伺服器端包括兩個部分:資料庫和一組 PHP 頁面,這些頁面允許您新增反饋、請求反饋列表並獲取與特定反饋相關的文章。

清單 1. Factory1.php
<?php
interface IUser
{
  function getName();
}

class User implements IUser
{
  public function __construct( $id ) { }

  public function getName()
  {
    return "Jack";
  }
}

class UserFactory
{
  public static function Create( $id )
  {
    return new User( $id );
  }
}

$uo = UserFactory::Create( 1 );
echo( $uo->getName()."\n" );
?>

IUser 介面定義使用者物件應執行什麼操作。IUser 的實現稱為 UserUserFactory 工廠類則建立IUser 物件。此關係可以用圖 1 中的 UML 表示。

圖 1. 工廠類及其相關 IUser 介面和使用者類
工廠類及其相關 IUser 介面和使用者類

如果您使用 php 直譯器在命令列上執行此程式碼,將得到如下結果:

% php factory1.php 
Jack
%

測試程式碼會向工廠請求 User 物件,並輸出 getName 方法的結果。

有一種工廠模式的變體使用工廠方法。類中的這些公共靜態方法構造該型別的物件。如果建立此型別的物件非常重要,此方法非常有用。例如,假設您需要先建立物件,然後設定許多屬性。此版本的工廠模式會將該程序封裝在單個位置中,這樣,不用複製複雜的初始化程式碼,也不必將複製好的程式碼在在程式碼庫中到處貼上。

清單 2 顯示使用工廠方法的一個示例。

清單 2. Factory2.php
<?php
interface IUser
{
  function getName();
}

class User implements IUser
{
  public static function Load( $id ) 
  {
        return new User( $id );
  }

  public static function Create( ) 
  {
        return new User( null );
  }

  public function __construct( $id ) { }

  public function getName()
  {
    return "Jack";
  }
}

$uo = User::Load( 1 );
echo( $uo->getName()."\n" );
?>

這段程式碼要簡單得多。它僅有一個介面 IUser 和一個實現此介面的 User 類。User 類有兩個建立物件的靜態方法。此關係可用圖 2 中的 UML 表示。

圖 2. IUser 介面和帶有工廠方法的 user 類
IUser 介面和帶有工廠方法的使用者類

在命令列中執行指令碼產生的結果與清單 1 的結果相同,如下所示:

% php factory2.php 
Jack
%

如上所述,有時此類模式在規模較小的環境中似乎有些大材小用。不過,最好還是學習這種紮實的編碼形式,以便應用於任意規模的專案中。

單例模式

某些應用程式資源是獨佔的,因為有且只有一個此型別的資源。例如,通過資料庫控制代碼到資料庫的連線是獨佔的。您希望在應用程式中共享資料庫控制代碼,因為在保持連線開啟或關閉時,它是一種開銷,在獲取單個頁面的過程中更是如此。

單元素模式可以滿足此要求。如果應用程式每次包含且僅包含一個物件,那麼這個物件就是一個單元素(Singleton)。清單 3 中的程式碼顯示了 PHP V5 中的一個數據庫連線單元素。

清單 3. Singleton.php
<?php
require_once("DB.php");

class DatabaseConnection
{
  public static function get()
  {
    static $db = null;
    if ( $db == null )
      $db = new DatabaseConnection();
    return $db;
  }

  private $_handle = null;

  private function __construct()
  {
    $dsn = 'mysql://root:[email protected]/photos';
    $this->_handle =& DB::Connect( $dsn, array() );
  }
  
  public function handle()
  {
    return $this->_handle;
  }
}

print( "Handle = ".DatabaseConnection::get()->handle()."\n" );
print( "Handle = ".DatabaseConnection::get()->handle()."\n" );
?>

此程式碼顯示名為 DatabaseConnection 的單個類。您不能建立自已的 DatabaseConnection,因為建構函式是專用的。但使用靜態get 方法,您可以獲得且僅獲得一個 DatabaseConnection 物件。此程式碼的 UML 如圖 3 所示。

圖 3. 資料庫連線單元素
資料庫連線單元素

在兩次呼叫間,handle 方法返回的資料庫控制代碼是相同的,這就是最好的證明。您可以在命令列中執行程式碼來觀察這一點。

% php singleton.php 
Handle = Object id #3
Handle = Object id #3
%

返回的兩個控制代碼是同一物件。如果您在整個應用程式中使用資料庫連線單元素,那麼就可以在任何地方重用同一控制代碼。

您可以使用全域性變數儲存資料庫控制代碼,但是,該方法僅適用於較小的應用程式。在較大的應用程式中,應避免使用全域性變數,並使用物件和方法訪問資源。

觀察者模式

觀察者模式為您提供了避免元件之間緊密耦合的另一種方法。該模式非常簡單:一個物件通過新增一個方法(該方法允許另一個物件,即觀察者 註冊自己)使本身變得可觀察。當可觀察的物件更改時,它會將訊息傳送到已註冊的觀察者。這些觀察者使用該資訊執行的操作與可觀察的物件無關。結果是物件可以相互對話,而不必瞭解原因。

一個簡單示例是系統中的使用者列表。清單 4 中的程式碼顯示一個使用者列表,新增使用者時,它將傳送出一條訊息。新增使用者時,通過傳送訊息的日誌觀察者可以觀察此列表。

清單 4. Observer.php
<?php
interface IObserver
{
  function onChanged( $sender, $args );
}

interface IObservable
{
  function addObserver( $observer );
}

class UserList implements IObservable
{
  private $_observers = array();

  public function addCustomer( $name )
  {
    foreach( $this->_observers as $obs )
      $obs->onChanged( $this, $name );
  }

  public function addObserver( $observer )
  {
    $this->_observers []= $observer;
  }
}

class UserListLogger implements IObserver
{
  public function onChanged( $sender, $args )
  {
    echo( "'$args' added to user list\n" );
  }
}

$ul = new UserList();
$ul->addObserver( new UserListLogger() );
$ul->addCustomer( "Jack" );
?>

此程式碼定義四個元素:兩個介面和兩個類。IObservable 介面定義可以被觀察的物件,UserList 實現該介面,以便將本身註冊為可觀察。IObserver 列表定義要通過怎樣的方法才能成為觀察者,UserListLogger 實現IObserver 介面。圖 4 的 UML 中展示了這些元素。

圖 4. 可觀察的使用者列表和使用者列表事件日誌程式
可觀察的使用者列表和使用者列表事件日誌程式

如果在命令列中執行它,您將看到以下輸出:

% php observer.php 
'Jack' added to user list
%

測試程式碼建立 UserList,並將 UserListLogger 觀察者新增到其中。然後新增一個消費者,並將這一更改通知UserListLogger

認識到 UserList 不知道日誌程式將執行什麼操作很關鍵。可能存在一個或多個執行其他操作的偵聽程式。例如,您可能有一個向新使用者傳送訊息的觀察者,歡迎新使用者使用該系統。這種方法的價值在於UserList 忽略所有依賴它的物件,它主要關注在列表更改時維護使用者列表併發送訊息這一工作。

此模式不限於記憶體中的物件。它是在較大的應用程式中使用的資料庫驅動的訊息查詢系統的基礎。

命令鏈模式

命令鏈 模式以鬆散耦合主題為基礎,傳送訊息、命令和請求,或通過一組處理程式傳送任意內容。每個處理程式都會自行判斷自己能否處理請求。如果可以,該請求被處理,程序停止。您可以為系統新增或移除處理程式,而不影響其他處理程式。清單 5 顯示了此模式的一個示例。

清單 5. Chain.php
<?php
interface ICommand
{
  function onCommand( $name, $args );
}

class CommandChain
{
  private $_commands = array();

  public function addCommand( $cmd )
  {
    $this->_commands []= $cmd;
  }

  public function runCommand( $name, $args )
  {
    foreach( $this->_commands as $cmd )
    {
      if ( $cmd->onCommand( $name, $args ) )
        return;
    }
  }
}

class UserCommand implements ICommand
{
  public function onCommand( $name, $args )
  {
    if ( $name != 'addUser' ) return false;
    echo( "UserCommand handling 'addUser'\n" );
    return true;
  }
}

class MailCommand implements ICommand
{
  public function onCommand( $name, $args )
  {
    if ( $name != 'mail' ) return false;
    echo( "MailCommand handling 'mail'\n" );
    return true;
  }
}

$cc = new CommandChain();
$cc->addCommand( new UserCommand() );
$cc->addCommand( new MailCommand() );
$cc->runCommand( 'addUser', null );
$cc->runCommand( 'mail', null );
?>

此程式碼定義維護 ICommand 物件列表的 CommandChain 類。兩個類都可以實現 ICommand 介面 —— 一個對郵件的請求作出響應,另一個對新增使用者作出響應。 圖 5 給出了 UML。

圖 5. 命令鏈及其相關命令
命令鏈及其相關命令

如果您執行包含某些測試程式碼的指令碼,則會得到以下輸出:

% php chain.php 
UserCommand handling 'addUser'
MailCommand handling 'mail'
%

程式碼首先建立 CommandChain 物件,併為它新增兩個命令物件的例項。然後執行兩個命令以檢視誰對這些命令作出了響應。如果命令的名稱匹配UserCommandMailCommand,則程式碼失敗,不發生任何操作。

為處理請求而建立可擴充套件的架構時,命令鏈模式很有價值,使用它可以解決許多問題。

策略模式

我們講述的最後一個設計模式是策略 模式。在此模式中,演算法是從複雜類提取的,因而可以方便地替換。例如,如果要更改搜尋引擎中排列頁的方法,則策略模式是一個不錯的選擇。思考一下搜尋引擎的幾個部分 —— 一部分遍歷頁面,一部分對每頁排列,另一部分基於排列的結果排序。在複雜的示例中,這些部分都在同一個類中。通過使用策略模式,您可將排列部分放入另一個類中,以便更改頁排列的方式,而不影響搜尋引擎的其餘程式碼。

作為一個較簡單的示例,清單 6 顯示了一個使用者列表類,它提供了一個根據一組即插即用的策略查詢一組使用者的方法。

清單 6. Strategy.php
<?php
interface IStrategy
{
  function filter( $record );
}

class FindAfterStrategy implements IStrategy
{
  private $_name;

  public function __construct( $name )
  {
    $this->_name = $name;
  }

  public function filter( $record )
  {
    return strcmp( $this->_name, $record ) <= 0;
  }
}

class RandomStrategy implements IStrategy
{
  public function filter( $record )
  {
    return rand( 0, 1 ) >= 0.5;
  }
}

class UserList
{
  private $_list = array();

  public function __construct( $names )
  {
    if ( $names != null )
    {
      foreach( $names as $name )
      {
        $this->_list []= $name;
      }
    }
  }

  public function add( $name )
  {
    $this->_list []= $name;
  }

  public function find( $filter )
  {
    $recs = array();
    foreach( $this->_list as $user )
    {
      if ( $filter->filter( $user ) )
        $recs []= $user;
    }
    return $recs;
  }
}

$ul = new UserList( array( "Andy", "Jack", "Lori", "Megan" ) );
$f1 = $ul->find( new FindAfterStrategy( "J" ) );
print_r( $f1 );

$f2 = $ul->find( new RandomStrategy() );
print_r( $f2 );
?>

此程式碼的 UML 如圖 6 所示。

圖 6. 使用者列表和用於選擇使用者的策略
使用者列表和用於選擇使用者的策略

UserList 類是打包名稱陣列的一個包裝器。它實現 find 方法,該方法利用幾個策略之一來選擇這些名稱的子集。這些策略由IStrategy 介面定義,該介面有兩個實現:一個隨機選擇使用者,另一個根據指定名稱選擇其後的所有名稱。執行測試程式碼時,將得到以下輸出:

% php strategy.php 
Array
(
    [0] => Jack
    [1] => Lori
    [2] => Megan
)
Array
(
    [0] => Andy
    [1] => Megan
)
%

測試程式碼為兩個策略運行同一使用者列表,並顯示結果。在第一種情況中,策略查詢排列在 J 後的任何名稱,所以您將得到 Jack、Lori 和 Megan。第二個策略隨機選取名稱,每次會產生不同的結果。在這種情況下,結果為 Andy 和 Megan。

策略模式非常適合複雜資料管理系統或資料處理系統,二者在資料篩選、搜尋或處理的方式方面需要較高的靈活性。

結束語

本文介紹的僅僅是 PHP 應用程式中使用的幾種最常見的設計模式。在設計模式 一書中演示了更多的設計模式。不要因架構的神祕性而放棄。模式是一種絕妙的理念,適用於任何程式語言、任何技能水平。



相關推薦

23設計模式解析常見PHP 設計模式

一、設計模式的分類 總體來說設計模式分為三大類: 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。 結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。 行為型模式,共十一種:策略模式、模板方

23設計模式解析-- 設計模式看這一篇就夠了

一、設計模式的分類 總體來說設計模式分為三大類: 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。 結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。 行為型模式,共十一種:策

23設計模式解析

一、設計模式的分類 總體來說設計模式分為三大類: 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。 結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。 行為型模式,共十一種:策略模式、模板

23軟體設計模式解析

一、設計模式的分類 總體來說設計模式分為三大類: 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。 結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。 行為型模式,共十一種:策略

OAuth2.0協議授權模式

OAuth:一個關於授權(authorization)的開放網路標準,目前版本是2.0版。 為何要使用OAuth協議呢?OAuth協議的應用場景。 第三方服務方提供服務,某些服務需要使用者的同意才能夠做到,好比客廳要裝修,需要得到主人的同意,拿到鑰匙,才能裝修,提

設計模式學習總結()創建者模式(Builder)

lose etl bfc .get splay hid 定義 string ogr   創建者模式,主要針對某些產品有類似的生產步驟,且有需要有先後順序的進行各個部件的生成。   一、示例展示:   通過學習及總結,以下是我完成的創建者模式的示例:   1.創建產品類:La

配置Spring項目上傳的兩方式(解析

enc element xml配置 很多 files dir 前言 name 兩種 歡迎查看Java開發之上帝之眼系列教程,如果您正在為Java後端龐大的體系所困擾,如果您正在為各種繁出不窮的技術和各種框架所迷茫,那麽本系列文章將帶您窺探Java龐大的體系。本系列教程希望

Android圖片載入框架最解析),Glide強大的圖片變換功能(筆記)

參考原文:Android圖片載入框架最全解析(五),Glide強大的圖片變換功能 一個問題 百度這張logo圖片的尺寸只有540258畫素,但是我的手機的解析度卻是10801920畫素,而我們將ImageView的寬高設定的都是wrap_content,那麼圖片的寬度應該只有

【Redis】Redis資料庫資料型別圖解

目錄 Redis資料庫: 是什麼? 優勢? 資料型別(五種): 1.字串: 2.hash型別: 3.list型別: 4.set型別: 5.zset型別: Redis資料庫: 是什麼? 一類新出現的,非關係型的,不支援SQL語法的,不支援事物,|

快速排序優化(模板)

1、快速排序的基本思想: 快速排序排序使用分治的思想,通過一趟排序將待排序列分割成兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小。之後分別對這兩部分記錄繼續進行排序,遞迴地以達到整個序列有序的目 2、快速排序的三個步驟: (1)選擇基準: 在待排序列中,按照某種方式

基於介面和子類的兩動態代理的解析使用

基於介面: package com.itheima.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.P

Java上帝之眼系列配置Spring專案檔案上傳兩方式(解析

歡迎檢視Java開發之上帝之眼系列教程,如果您正在為Java後端龐大的體系所困擾,如果您正在為各種繁出不窮的技術和各種框架所迷茫,那麼本系列文章將帶您窺探Java龐大的體系。本系列教程希望您能站在上帝

配置Spring專案上傳的兩方式(解析

歡迎檢視Java開發之上帝之眼系列教程,如果您正在為Java後端龐大的體系所困擾,如果您正在為各種繁出不窮的技術和各種框架所迷茫,那麼本系列文章將帶您窺探Java龐大的體系。本系列教程希望您能站在上帝的角度去觀察(瞭解)Java體系。使Java的各種後端技術在你心中模組化;讓你在工作中能將Java各個技術瞭

執行緒的生命週期基本狀態

生命週期的五種狀態 新建(new Thread) 當建立Thread類的一個例項(物件)時,此執行緒進入新建狀態(未被啟動)。 例如:Thread  t1=new Thread(); 就緒(runnable) 執行緒已經被啟動,正在等待被分配給CPU時間片,也就是說此

Android圖片載入框架最解析),Glide強大的圖片變換功能

                       本文同步發表於我的微信公眾號,掃一掃文章底部的二維碼或在微信搜尋 郭霖 即可關注,每天都有文章更新。大家好,又到了學習Glide的時間了。前段時間由於專案開發緊張,再加上後來又生病了,所以停更了一個月,不過現在終於又可以恢復正常更新了。今天是這個系列的第五篇文章,

第X屆智慧車攝像頭組程式碼解析------()電機PID初始化

作者:Sumjess   本次部落格內容: 該初始化函式下有以下語句: 一、ftm_pwm_init();  ---  電機初始化: 直接呼叫山外的庫函式,該函式內部並無修改。     ftm_pwm

spring事務(Transaction)的七事務傳播行為隔離級別

1. 首先,說說什麼事務(Transaction) 事務,就是一組操作資料庫的動作集合。事務是現代資料庫理論中的核心概念之一。 如果一組處理步驟或者全部發生或者一步也不執行,我們稱該組處理步驟為一個事務。 當所有的步驟像一個操作一樣被完整地執行,我們稱該事務被

Docker使用 linuxserver/letsencrypt 生成SSL證書最解析實踐

本文使用http和dns兩種校驗方式對docker下linuxserver/letsencrypt 專案進行了實踐,並使用nginx的htpasswd來對網站進行密碼保護,並測試使用fail2ban防止htpasswd被暴力破解.全文基於linuxserver/

ELF格式檔案符號表解析readelf命令使用方法

readelf 命令引數 以 hello.c 程式為例 #include <stdio.h> #include <stdlib.h> int main() { int a=100; print

Java 執行緒生命週期狀態

一、執行緒的生命週期 關於Java中執行緒的生命週期,如圖: 上圖中基本上包括了Java中多執行緒各重要知識點。掌握了上圖中的各知識點,Java中的多執行緒也就基本上掌握了。 主要包括: Java執行緒具有五中基本狀態 新建狀態(New):當執行緒物件對建立後,即進入了新建