設計模式的相關介紹

echo_dump發表於2020-06-21

學習PHP設計模式的相關記錄

  1. 什麼是設計模式

軟體設計模式(Design pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用
程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性、程式的重用性

為什麼要使用設計模式,以上說的很明白,就是為了讓程式碼可以複用,解除程式碼之間的高耦合低內聚,減少無用的程式碼。讓一個專案增加可維護性,更加容易擴充套件,同時
也是使程式更加的健壯,更加的可靠,更加的靈活,知道了什麼使設計模式,為什麼要去使用設計模式,那麼設計模式具體又怎麼劃分的呢?

  • ps: 一般學習設計模式的時候都是,最開始什麼是設計模式,那裡有設計模式,原來這就是設計模式,我也要使用設計模式,哪兒都想使用設計模式,再一看
    哪兒都使用不了設計模式,最後乾脆哪兒都使用設計模式,到頭來才發現,使用的設計模式哪兒哪兒都不合適,最後放棄了設計模式,寫程式碼的慣性思維之後,不
    知不覺用到了設計模式,自己也不知道使用了設計模式,原來這就是設計模式了。(就如倚天屠龍記裡,張真人教張無忌太極,一開始全都記得,過一會,十之七
    八,在一會,十之五六,在一會十之三四,在一會呢,全沒了),就和這個道理一樣,所以切記別急。
  1. 設計模式大致可分為三大類
  • 建立型

所謂建立型的設計模式,個人認為就是,以合適的的結構來建立一個物件,是屬於從無到有的,透過以合適的方法,結構,形式來建立一個物件。物件的建立的基
本形式可能帶來設計問題,或者增加了設計的複雜度,建立型的設計模式就是透過控制物件的建立方式(形式)來解決這個問題,比如經典(簡單)的單例模式,
如果在一個程式中,經常使用一個類,每次使用它,都要new,而不間斷的new同一個物件,這就造成了大量的資源浪費,剛好單例模式就可以解決這個問題

  • 結構型

結構型的設計模式,就是從一個程式的結構上來操刀,讓不同的類(物件)之間相互組合(結合),解決程式模組上的耦合問題,解釋瞭如何將物件和類組裝為
更大的結構,同時又保持結構的靈活性和效率。

  • 行為型

將類(物件)與物件之間的互動,職責劃分規定明確,負責有效的溝通以及物件之間的責任分配,比如觀察者模式,這其中就定義了誰是觀察者,誰是被觀察者,
以及它們各自需要做的事情,安排的明明白白

  1. 設計模式的六大原則
  • 單一職責

簡單來講:對於一個類,那這一個類應該只負責一項職責

  • ps 比如有一個學校,學校有學生,有老師,每個班級都有老師,通常來說,每個班級都有一個班主任負責,而除了班主任之外,還有這個班級的代課老師,每
    一門學科都有一個專門的老師負責,語文-》語文老師,數學-》數學老師,英文-》英語老師,體育-》體育老師,等等。如果職責劃分不清楚,一個老師負責一
    個班級的所有課程,那麼如果這個老師那一天有事情需要請假,那麼這個老師的所有課程都沒有辦法正常上課了,如果這個老師只是負責當前班級的一門課程,那
    他有事情的時候,還可以請假,又另外一個沒有課程的老師代課。
class Teacher
{
    protected $name;

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

    public function subject(string $class, string $subject)
    {
        echo '早上8:45,第一節課是' . $this->name . '老師正在給 (' . $class . ')班上'.$subject.'課' . PHP_EOL;
    }
}

$teacher = new Teacher('王');
$teacher->subject('一', '語文');

$teacher->subject('二', '數學');
$teacher->subject('三', '體育');
$teacher->subject('四', '英文');


//早上8:45,第一節課是王老師正在給 (一)班上語文課
//早上8:45,第一節課是王老師正在給 (二)班上數學課
//早上8:45,第一節課是王老師正在給 (三)班上體育課
//早上8:45,第一節課是王老師正在給 (四)班上英文課
  • ps 再比如現在有一個交通工具的類,類裡面有一個run()方法
class TrafficTest
{
    public function run(string $name)
    {
        echo $name .  ' 在公路上行駛' . PHP_EOL;
    }
}
$traffic = new TrafficTest();
//汽車
$traffic->run('汽車');
//輪船
$traffic->run('輪船');
//飛機
$traffic->run('飛機');

/**
* 汽車 在公路上行駛
  輪船 在公路上行駛
  飛機 在公路上行駛
 */

這個run()方法接收不同的引數,不同的交通工具在裡面都可以執行,但是結果卻不是最好的,汽車是可以在公路上行駛,但是飛機是在空中飛的,輪船是在
水裡的,都在公路上了,就不對了,這樣的一個方法執行一個汽車就可以了,它做的太多了,結果卻做不好,我們可以最佳化一下,讓它只做一件事兒,比如方法一
run()方法不動,把它定義為一個介面,定義三個字類(汽車類,輪船類,飛機類)來實現這個run()方法,這樣就是一個類作為單一行為來區分,這樣更加
明確,但是唯一的缺點,要建立很多的不同類。方法二:在這個交通方法裡,分別建立三個不同的方法,每個方法只做與它相關的事兒,比如下面這樣,就可以保證
它們之間是不會相互影響的。

class TrafficTest
{
    public function carRun()
    {
        echo '汽車在公路上行駛' . PHP_EOL;
    }

    public function aircraftRun()
    {
        echo '飛機在天空飛行' . PHP_EOL;
    }

    public function shipRun()
    {
        echo '輪船在大海里航行' . PHP_EOL;
    }
}
  • 介面隔離

客戶端不應該依賴它不需要的介面,即一個類對另外一個類的依賴應該建立在最小的介面上

比如我們有一個介面BaseInterface,裡面定義了index(), show(), edit(), update(), delete()這五個介面

interface BaseInterface 
{
    public function index();

    public function show();

    public function edit();

    public function update();

    public function delete();
}

現在有一個Home類,這個類是要去實現這個BaseInterface的介面,但是它只需要其中的一個方法index(),剩下的介面,它都不需要,但是它繼承了
這個上一層的介面,就必須實現它裡面的所有方法,所以必須有一個預設實現

class Home implements BaseInterface
{

    public function index()
    {
        // TODO: Implement index() method.
        echo '檢視全部的入口' . PHP_EOL;
    }

    public function show()
    {
        // TODO: Implement show() method.
    }

    public function edit()
    {
        // TODO: Implement edit() method.
    }

    public function update()
    {
        // TODO: Implement update() method.
    }

    public function delete()
    {
        // TODO: Implement delete() method.
    }
}

還有一個類Post這個是一個管理所有的文章的類,它需要去實現BaseInterface的除了delete()方法的所有介面

class Post implements  BaseInterface
{

    public function index()
    {
        // TODO: Implement index() method.
        echo '檢視文章列表' . PHP_EOL;
    }

    public function show()
    {
        // TODO: Implement show() method.
        echo '檢視文章' . PHP_EOL;
    }

    public function edit()
    {
        // TODO: Implement edit() method.
        echo '編輯文章' . PHP_EOL;
    }

    public function update()
    {
        // TODO: Implement update() method.
        echo '更新文章' . PHP_EOL;
    }

    public function delete()
    {
        // TODO: Implement delete() method.
    }
}

最後一個管理員的類Admin也需要去實現BaseInterface的介面,而它是需要實現除了index()之外它所有的介面

class Admin implements BaseInterface
{

    public function index()
    {
        // TODO: Implement index() method.
    }

    public function show()
    {
        // TODO: Implement show() method.
        echo '檢視使用者' . PHP_EOL;
    }

    public function edit()
    {
        // TODO: Implement edit() method.
        echo '編輯新增使用者' . PHP_EOL;
    }

    public function update()
    {
        // TODO: Implement update() method.
        echo '修改使用者' . PHP_EOL;
    }

    public function delete()
    {
        // TODO: Implement delete() method.
        echo '刪除使用者' . PHP_EOL;
    }
}

這個時候,我們就會發現,Home類只需要index()方法,Post類需要除了delete()方法之外的所有方法,Admin類需要除了index()方法之外
的所有方法,裡面有很多方法都是當前這個類所不需要的,但是,還是要去實現它,這樣一來是一種浪費,資源的開銷,二來也不符合設計模式的介面隔離原則,
是很不友好的。如果我們把BaseInterface這個介面分為三個小的介面,下面每個字類只需要去對應實現各自需要的介面,互不干擾,這樣的話就會使程式看
起來很舒服,很安全,健壯。你如這樣實現上面的例子:

interface BaseInterface1
{
    public function index();
}

interface BaseInterface2 
{
    public function show();

    public function edit();

    public function update();
}

interface BaseInterface3
{
    public function delete();
}

class Home1 implements BaseInterface1
{

    public function index()
    {
        // TODO: Implement index() method.
    }
}

class Post1 implements BaseInterface1, BaseInterface2
{

    public function index()
    {
        // TODO: Implement index() method.
    }

    public function show()
    {
        // TODO: Implement show() method.
    }

    public function edit()
    {
        // TODO: Implement edit() method.
    }

    public function update()
    {
        // TODO: Implement update() method.
    }
}

class Admin1 implements BaseInterface2, BaseInterface3
{

    public function show()
    {
        // TODO: Implement show() method.
    }

    public function edit()
    {
        // TODO: Implement edit() method.
    }

    public function update()
    {
        // TODO: Implement update() method.
    }

    public function delete()
    {
        // TODO: Implement delete() method.
    }
}
  • 依賴倒置
  1. 高層模組不應該依賴底層模組,二者都應該依賴抽象

  2. 抽象不應該依賴細節,細節應該依賴抽象

  3. 依賴倒置的中心思想就是面向介面程式設計

  4. 相對於細節的多變性,抽象比細節要穩定的多,以抽象搭建的東西比以細節搭建的東西穩定的的多,(抽象在這裡可以抽象類,介面,細節就是具體實現類)

  5. 使用抽象和介面的目的就是制定好規範,而不涉及具體的操作,把實現細節的任務交給具體的類去實現

比如現在有一個接收資訊處理的類Person,裡面有一個接受訊息的方法getInfo(),這裡是接受Emali的訊息,所有我們把Email類作為引數傳遞進去

class Email
{
    public function sendInfo()
    {
        echo '傳送郵件訊息' . PHP_EOL;
    }
}

class Person
{
    public function getInfo(Email $email)
    {
        $email->sendInfo();
    }
}

現在應為需求變化,Person類還要可以接收簡訊類的訊息,這個時候,這個getInfo()方法因為是固定引數為Email,就代表現有的方法,是不能使用的,
要去修改getInfo()的引數,或者另外再加一個方法,這樣做的話,就破壞了這個類的封裝,沒有一個好的擴充套件性,不利於維護,如果下次再加一個傳送訊息
的渠道,還要去修改,所有我們可以把所有傳送訊息的型別定義為一個介面,下面是各種訊息型別的實現,而getInfo()方法也是去依賴這個介面,不是去依賴
具體的實現,不管以後要新增多少傳送訊息的種類,只需要去實現這個訊息的介面就可以了,在接受訊息類裡面,我們是需要去改變一絲一毫的。比如:

interface InfoInterface
{
    public function sendInfo();
}

class Email1 implements InfoInterface
{

    public function sendInfo()
    {
        // TODO: Implement sendInfo() method.
        echo '傳送郵件訊息' . PHP_EOL;
    }
}

class SMS implements InfoInterface
{

    public function sendInfo()
    {
        // TODO: Implement sendInfo() method.
        echo '傳送簡訊訊息' . PHP_EOL;
    }
}

class Person1
{
    public function getInfo(InfoInterface $info)
    {
        $info->sendInfo();
    }
}

依賴倒置的三種方式:1.透過介面依賴 2. 透過構造方法實現依賴 3. 同構setAttritute()方法實現依賴

  • 里氏替換
  1. 如果有一個型別為T1的物件obj1,它有一個型別為T2的物件obj2,換句話說就是obj2繼承了obj1,當一個程式P在使用T1所定義的所有方法換成T2所有的方法的
    時候,程式P的執行結果並沒有改變,(行為沒有改變,所有引用基類的地方都必須能夠可以透明的使用其子類的物件)

  2. 使用繼承時,遵循里氏替換原則,在字類中儘量不要重寫父類的方法

  3. 里氏替換原則告訴我們,繼承實際上是讓兩個類之間的耦合性增強了,在適當的情況下,可以透過組合,聚合,依賴來解決問題

  • 開閉
  1. 一個軟體實體類,模組,函式應該對擴充套件開放(對提供方),對修改關閉(對使用方),用抽象構建框架,用實現擴充套件細節

  2. 當軟體變化的時候,儘量透過擴充套件軟體實體類的行為來實現變化,而不是透過修改已有的程式碼來實現變化

  3. 例子:設計一個畫圖的軟體,可以畫各種圖形,

不好的實現:在新增另外一個畫圖方法的時候,首先需要先繼承Type抽象類,設定一個type的型別,然後再在Drawing類裡drawing()方法新增一個
switch型別,最後再加一個畫圖的方法,供它呼叫,需要修改的地方有很多。

class OpenAndCloseTest
{
    public function __construct()
    {
        $drawing = new Drawing();
        $drawing->drawing(new Circular());
        $drawing->drawing(new triangle());
    }
}

class Drawing
{
    public function drawing(Type $type)
    {
        switch ($type->type) {
            case 1:
                $this->dropCircular();
                break;
            case 2:
                $this->dropTriangle();
                break;
        }
    }

    public function dropCircular()
    {
        echo "畫圓形" . PHP_EOL;
    }

    public function dropTriangle()
    {
        echo "畫三角形" . PHP_EOL;
    }
}

abstract class Type
{
    public $type;
}

class Circular extends Type
{
    public function __construct()
    {
        $this->type = 1;
    }
}

class triangle extends Type
{
    public function __construct()
    {
        $this->type = 2;
    }
}

new OpenAndCloseTest();

好的實現:各個畫圖的方法就是繼承畫圖的基類,然後在各自類中自己實現,在畫圖的工具類中,也不需要再去加判斷了,只需要呼叫畫圖的方法就好了,想要
畫那個圖形,在類外面傳入進去就可以了,如果以後還想要擴充套件,也只需要去繼承一個畫圖的基類,然後自己去實現,別的任何一個地方的程式碼都不需要修改

class OpenAndCloseUpgradeTest
{
    public function __construct()
    {
        $drawingTest = new DrawingTest();

        $drawingTest->draw(new CircularTest());

        $drawingTest->draw(new triangleTest());
    }
}

class DrawingTest
{
    public function draw(TypeTest $draw)
    {
        $draw->drawing();
    }
}

abstract class TypeTest
{
    abstract public function drawing();
}

class CircularTest extends TypeTest
{

    public function drawing()
    {
        // TODO: Implement drawing() method.
        echo "畫圓形" . PHP_EOL;
    }
}

class triangleTest extends TypeTest
{

    public function drawing()
    {
        // TODO: Implement drawing() method.
        echo "畫三角形" . PHP_EOL;
    }
}

new OpenAndCloseUpgradeTest();
  • 迪米特
  1. 一個物件應該對其它物件保持最少的瞭解

  2. 類與類關係越密切,耦合度越大

  3. 迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內部,對愛只提供一個
    public方法,不對外洩露任何資訊

  4. 只與直接的朋友通訊(直接的朋友,成員變數,方法引數,方法返回值中的類為直接朋友,出現在區域性變數中的類不是直接朋友)

  • 合成複用

儘量使用合成/聚合方式來代替繼承

本作品採用《CC 協議》,轉載必須註明作者和本文連結
LIYi ---- github地址

相關文章