PHP面向對象設計的五大原則

面向對象設計的五大原則:單一職責原則、接口隔離原則、開放-封閉原則、替換原則、依賴倒置原則。這些原則主要是由Robert C.Martin在《敏捷軟件開發——原則、方法、與實踐》一書中總結出來,這五大原則也是23種設計模式的基礎。php

單一職責原則 Single Pesponsibility Principle, SRPmysql

在MVC框架中,對於表單插入數據庫字段過濾與安全檢查應該是放在control層處理仍是model層處理,這類問題均可以歸到單一職責的範圍。sql

單一職責有兩個含義:數據庫

  1. 避免相同的職責分散到不一樣的類中
  2. 一個類承擔太多職責

遵照SRP的好處:編程

  1. 減小類之間的耦合
  2. 提升類的複用性

在實際代碼開發中的應用:工廠模式、命令模式、代理模式等。
工廠模式(Factory)容許在代碼執行時實例化對象。之因此被稱爲工廠模式是由於它負責「生產」對象。以數據庫爲例,工廠須要的就是根據不一樣的參數,生成不一樣的實例化對象。它只負責生產對象,而不負責對象的具體內容。設計模式

定義一個適配器接口:安全

<?php
interface DbAdapter 
{
    /**
    * 數據庫鏈接
    * @param $config 數據庫配置
    * @return resource
    */
    public function connect($config);
    
    /**
    * 執行數據庫查詢
    * @param string $query 數據庫查詢SQL字符串
    * @param mixed $handle 鏈接對象
    * @return resource
    */
    public function query($query, $handle);
}
?>

定義MySQL數據庫操做類:框架

<?php
class DbAdapterMysql implements DbAdapter
{
    private $_dbLink; //數據庫鏈接字符串表示

    /**
    * 數據庫鏈接函數
    * @param $config 數據庫配置
    * @throws DbException
    * @return resource
    */
    public function connect($config)
    {
        if($this->_dbLink = @mysql_connect($config->host .
            (empty($config->port) ? '' : ':' . $config->port),
        $config->user, $config->password, true)) {
            if(@mysql_select_db($config->database, $this->_dbLink)){
                if($config->charset){
                    mysql_query("SET NAMES '{$config->charset}'", $this->_dbLink);
                }
                return $this->_dbLink;
            }
        }
        //數據庫異常
        throw new DbException(@mysql_error($this->_dbLink));
    }
    
    /**
    * 執行數據庫查詢
    * @param string $query 數據庫查詢SQL字符串
    * @param mixed $handle 鏈接對象
    * @return resource
    */
    public function query($query, $handle)
    {
        if ($resource = @mysql_query($query, $handle)) {
            return $resource;
        }
    }
}
?>

SQLite數據庫操做類:函數

<?php
class DbAdapterSqlite implements DbAdapter
{
    private $_dbLink;
    
    /**
    * 數據庫鏈接函數
    * @param $config 數據庫配置
    * @throws DbException
    * @return resource
    */
    public function connect($config)
    {
        if ($this->_dbLink = sqlite_open($config->file, 0666, $error)) {
            return $this->_dbLink;
        }
        
        throw new DbException($error);
    }
    
    /**
    * 執行數據庫查詢
    * @param string $query 數據庫查詢SQL字符串
    * @param mixed $handle 鏈接對象
    * @return resource
    */
    public function query($query, $handle)
    {
        if ($resource = @sqlite_query($query, $handle)) {
            return $resource;
        }
    }
}
?>

定義一個工廠類,根據傳入不一樣的參數生成須要的類:this

<?php
    class sqlFactory
    {
        public static function factory($type)
        {
            if (include_once 'Drivers/' . $type . '.php') {
                $classname = 'DbAdapter' . $type;
                return new $classname;
            } else {
                throw new Exception('Driver not found');
            }
        }
    }
?>

調用:

$db = sqlFactory::factory('MySQL');
$db = sqlFactory::factory('SQLite');

命令模式分離「命令的請求者」和「命令的實現者」方面的職責。

<?php
/**
* 廚師類,命令接受者與執行者
*/
class cook 
{
    public function meal(){
        echo '番茄炒雞蛋',PHP_EOL;
    }
    
    public function drink(){
        echo '紫菜蛋花湯',PHP_EOL;
    }
    
    public function ok(){
        echo '完畢',PHP_EOL;
    }
}

/**
* 命令接口
*/
interface Command{
    public function execute();
}
?>

模擬服務員與廚師的過程:

<?php
class MealCommand implements Command
{
    private $cook;
    
    //綁定命令接受者
    public function __construct(cook $cook){
        $this->cook = $cook;
    }
    
    public function execute(){
        $this->cook->meal();//把消息傳遞給廚師,讓廚師作飯
    }
}

class DrinkCommand implements Command
{
    private $cook;
    
    //綁定命令接受者
    public function __construct(cook $cook){
        $this->cook = $cook;
    }
    
    public function execute(){
        $this->cook->drink();
    }
}
?>

模擬顧客與服務員的過程:

<?php
class cookControl
{
    private $mealCommand;
    private $drinkCommand;
    
    //將命令發送者綁定到命令接收器
    public function addCommand(Command $mealCommand, Command $drinkCommand){
        $this->mealCommand = $mealCommand;
        $this->drinkCommand = $drinkCommand;
    }
    
    public function callMeal(){
        $this->mealCommand->execute();
    }
    
    public function callDrink(){
        $this->drinkCommand->execute();
    }
}    
?>

實現命令模式:

$control = new cookControl;
$cook = new cook;
$mealCommand = new MealCommand($cook);
$drinkCommand = new DrinkCommand($cook);
$control->addCommand($mealCommand, $drinkCommand);
$control->callMeal();
$control->callDrink();

接口隔離原則 Interface Segregation Principle,ISP
接口隔離原則(Interface Segregation Principle,ISP)代表客戶端不該該被強迫實現一些不會使用的接口,應該把胖接口分組,用多個接口代替它,每一個接口服務於一個子模塊。簡單地說,就是使用多個專門的接口比使用單個接口要好不少。
ISP主要觀點:
1.一個類對另一個類的依賴性應當是創建在最小接口上的。
ISP能夠達到不強迫客戶(接口使用者)依賴於他們不用的方法,接口的實現類應該只呈現爲單一職責的角色(遵照SRP原則)。
ISP能夠下降客戶之間的相互影響——當某個客戶程序要求提供新的職責(需求變化)而迫使接口發生變化時,影響到其餘客戶程序的可能性會最小。
2.客戶端程序不該該依賴它不須要的接口方法(功能)。

ISP強調的是接口對客戶端的承諾越少越好,而且要作到專注。
接口污染就是爲接口添加沒必要要的職責。「接口隔離」其實就是定製化服務設計的原則。使用接口的多重繼承實現對不一樣的接口的組合,從而對外提供組合功能——達到「按需提供服務」。

對於接口的污染,使用下面兩種處理方式:

  • 利用委託分離接口。
  • 利用多繼承分離接口。

委託模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理,如策略模式、代理模式等都應用到了委託的概念。

開放-封閉原則
隨着軟件系統的規模不斷增大,軟件系統的維護和修改的複雜性不斷提升,這種困境促使法國工程院士Bertrand Meyer在1998年提出了「開放-封閉」(Open-Close Principle, OCP)原則,基本思想是:
Open(Open for extension)模塊的行爲必須是開放的、支持擴展的,而不是僵化的。
Closed(Closed for modification)在對模塊的功能進行擴展時,不該該影響或大規模地影響已有的程序模塊。

換句話說,也就是要求開發人員在不修改系統中現有功能代碼(源代碼或二進制代碼)的前提下,實現對應用系統的軟件功能的擴展。用一句話歸納就是:一個模塊在擴展性方面應該是開放的而在更改性方面應該是封閉的
開放-封閉可以提升系統的可擴展性和可維護性,但這也是相對的。
以播放器爲例,先定義一個抽象的接口:

interface Process
{
    public function process();
}

而後對此接口進行擴展,實現解碼和輸出的功能:

class playerEncode implements Proess
{
    public function process(){
        echo "encode\r\n";
    }
}    

class playerOutput implements Process
{
    public function process(){
        echo "output\r\n";
    }
}

對於播放器的各類功能,這裏是開放的。只要你遵照約定,實現了process接口,就能給播放器添加新的功能模塊。
接下來爲定義播放器的線程調度管理器,播放器一旦接收到通知(能夠是外部單擊行爲,也能夠是內部的notify行爲),將回調實際的線程處理:

class playProcess
{
    private $message = null;
    public function __construct(){
    }
    
    public function callback(Event $event){
        $this->message = $event->click();
        if($this->message instanceof Process){
            $this->message->process();
        }
    }
}

具體的產品出來了,在這裏定義一個MP4類,這個類是相對封閉的,其中定義事件的處理邏輯:

class MP4
{
    public function work(){
        $playProcess = new playProcess();
        $playProcess->callback(new Event('encode'));
        $playProcess->callback(new Event('output'));
    }
}

最後爲事件分揀的處理類,此類負責對事件進行分揀,判斷用戶或內部行爲,以產生正確的「線程」,供播放器內置的線程管理器調度:

class Event
{
    private $m;
    
    public function __construct($me){
        $this->m = $me;
    }
    
    public function click(){
        switch($this->m){
            case 'encode':
                return new playerEncode();
                break;
            case 'output':
                return new playerOutput();
                break;    
        }
    }
}

運行:

$mp4 = new MP4;
$mp4->work();
//打印結果
encode
output

如何遵照開放-封閉原則
實現開放-封閉的核心思想就是抽象編程的核心思想就是對抽象編程,而不是對具體編程,由於抽象相對穩定。讓類依賴於固定的抽象,這樣的修改就是封閉的;而經過面向對象的繼承和多態機制,能夠實現對抽象體的繼承,經過覆寫其方法來改變固有的行爲,實現新的擴展方法,因此對於擴展就是開放的。
1.在設計方面充分應用「抽象」和封裝的思想。
一方面就是要在軟件系統中找出各類可能的「可變因素」,並將之封裝起來;另外一方面,一種可變性因素不該當散落在多個不一樣代碼模塊中,而應當被封裝到一個對象中。
2.在系統功能編程實現方面應用面向接口編程。
當需求發生變化時,能夠提供該接口新的實現類,以求適應變化。
面向接口編程要求功能類實現接口,對象聲明爲接口類型。再設計模式中,裝飾模式比較明顯地用到OCP。

替換原則
替換原則也稱里氏替換原則(Liskov Substitution Principle, LSP)的定義和主要思想以下:因爲面向對象編程技術中的繼承在具體的編程中過於簡單,在許多系統的設計和編程實現中,咱們並無認真地、理性地思考應用系統中各個類之間的繼承關係是否合適,派生類是否能正確地對其基類中的某些方法進行重寫等問題。所以常常出現濫用繼承或者錯誤地進行了繼承等現象,給系統的後期維護帶來很多麻煩。
LSP指出:子類型必須可以替換掉它們的父類型,並出如今父類可以出現的任何地方
LSP主要是針對繼承的設計原則,繼承與派生(多態)是OOP的主要特性。
如何遵照LSP設計原則:

  • 父類的方法都要在子類中實現或重寫,而且派生類只實現其抽象類中聲明的方法,而不該當給出多餘的方法定義或實現。

在客戶段程序中只應該使用父類對象而不該當直接使用子類對象,這樣能夠實現運行期綁定(動態綁定)。
若是A、B兩個類違反了LSP的設計,一般的作法是建立一個新的抽象類C,做爲兩個具體類的超類,將A和B的共同行爲移到C中,從而解決A和B行爲不徹底一致的問題。

依賴倒置原則 Dependence Inversion Principle, DIP
依賴倒置簡單地講就是將依賴關係倒置爲依賴接口,具體概念以下:

  • 上層模塊不該該依賴於下層模塊,它們共同依賴於一個抽象(父類不能依賴子類,它們都要依賴抽象類)。
  • 抽象不能依賴於具體,具體應該依賴於抽象。

爲何要依賴接口?由於接口體現對問題的抽象,同時因爲抽象通常是相對穩定的或者是相對變化不頻繁的,而具體是易變的。所以,依賴抽象是實現代碼擴展和運行期內綁定(多態)的基礎:只要實現了該抽象類的子類,均可以被類的使用者使用。

<?php
interface employee
{
    public function working();
}

class teacher implements employee
{
    public function working(){
        echo 'teaching...';
    }
}

class coder implements employee
{
    public function working(){
        echo 'coding...';
    }
}

class workA
{
    public function work(){
        $teacher = new teacher;
        $teacher->working();
    }
}

class workB
{
    private $e;
    public function set(employee $e){
        $this->e = $e;
    }
    
    public function work(){
        $this->e->working();
    }
}

$worka = new workA;
$worka->work();
$workb = new workB;
$workb->set(new teacher());
$workb->work();

在workA中,work方法依賴於teacher實現;在workB中,work轉而依賴於抽象,這樣能夠把須要的對象經過參數傳入。在workB中,teacher實例經過setter方法傳入,從而實現了工廠模式。因爲這樣的是實現是硬編碼的,爲了實現代碼的進一步擴展,把這個依賴關係寫在配置文件裏,指明workB須要一個teacher對象,專門由一個程序檢測配置是否正確(如所依賴的類文件是否存在)以及加載配置中所依賴的實現,這個檢測程序,就稱爲IOC容器。IOC(Inversion Of Control)是依賴倒置原則(Dependence Inversion Principle, DIP)的同義詞。依賴注入(DI)和依賴查找(DS)是IOC的兩種實現。

相關文章
相關標籤/搜索