學習PHP設計模式的相關記錄
- 什麼是設計模式
軟體設計模式(Design pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用
程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性、程式的重用性
為什麼要使用設計模式,以上說的很明白,就是為了讓程式碼可以複用,解除程式碼之間的高耦合低內聚,減少無用的程式碼。讓一個專案增加可維護性,更加容易擴充套件,同時
也是使程式更加的健壯,更加的可靠,更加的靈活,知道了什麼使設計模式,為什麼要去使用設計模式,那麼設計模式具體又怎麼劃分的呢?
- ps: 一般學習設計模式的時候都是,最開始什麼是設計模式,那裡有設計模式,原來這就是設計模式,我也要使用設計模式,哪兒都想使用設計模式,再一看
哪兒都使用不了設計模式,最後乾脆哪兒都使用設計模式,到頭來才發現,使用的設計模式哪兒哪兒都不合適,最後放棄了設計模式,寫程式碼的慣性思維之後,不
知不覺用到了設計模式,自己也不知道使用了設計模式,原來這就是設計模式了。(就如倚天屠龍記裡,張真人教張無忌太極,一開始全都記得,過一會,十之七
八,在一會,十之五六,在一會十之三四,在一會呢,全沒了),就和這個道理一樣,所以切記別急。
- 設計模式大致可分為三大類
- 建立型
所謂建立型的設計模式,個人認為就是,以合適的的結構來建立一個物件,是屬於從無到有的,透過以合適的方法,結構,形式來建立一個物件。物件的建立的基
本形式可能帶來設計問題,或者增加了設計的複雜度,建立型的設計模式就是透過控制物件的建立方式(形式)來解決這個問題,比如經典(簡單)的單例模式,
如果在一個程式中,經常使用一個類,每次使用它,都要new
,而不間斷的new
同一個物件,這就造成了大量的資源浪費,剛好單例模式就可以解決這個問題
- 結構型
結構型的設計模式,就是從一個程式的結構上來操刀,讓不同的類(物件)之間相互組合(結合),解決程式模組上的耦合問題,解釋瞭如何將物件和類組裝為
更大的結構,同時又保持結構的靈活性和效率。
- 行為型
將類(物件)與物件之間的互動,職責劃分規定明確,負責有效的溝通以及物件之間的責任分配,比如觀察者模式,這其中就定義了誰是觀察者,誰是被觀察者,
以及它們各自需要做的事情,安排的明明白白
- 設計模式的六大原則
- 單一職責
簡單來講:對於一個類,那這一個類應該只負責一項職責
- 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.
}
}
- 依賴倒置
高層模組不應該依賴底層模組,二者都應該依賴抽象
抽象不應該依賴細節,細節應該依賴抽象
依賴倒置的中心思想就是面向介面程式設計
相對於細節的多變性,抽象比細節要穩定的多,以抽象搭建的東西比以細節搭建的東西穩定的的多,(抽象在這裡可以抽象類,介面,細節就是具體實現類)
使用抽象和介面的目的就是制定好規範,而不涉及具體的操作,把實現細節的任務交給具體的類去實現
比如現在有一個接收資訊處理的類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()
方法實現依賴
- 里氏替換
如果有一個型別為T1的物件obj1,它有一個型別為T2的物件obj2,換句話說就是obj2繼承了obj1,當一個程式P在使用T1所定義的所有方法換成T2所有的方法的
時候,程式P的執行結果並沒有改變,(行為沒有改變,所有引用基類的地方都必須能夠可以透明的使用其子類的物件)使用繼承時,遵循里氏替換原則,在字類中儘量不要重寫父類的方法
里氏替換原則告訴我們,繼承實際上是讓兩個類之間的耦合性增強了,在適當的情況下,可以透過組合,聚合,依賴來解決問題
- 開閉
一個軟體實體類,模組,函式應該對擴充套件開放(對提供方),對修改關閉(對使用方),用抽象構建框架,用實現擴充套件細節
當軟體變化的時候,儘量透過擴充套件軟體實體類的行為來實現變化,而不是透過修改已有的程式碼來實現變化
例子:設計一個畫圖的軟體,可以畫各種圖形,
不好的實現:在新增另外一個畫圖方法的時候,首先需要先繼承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();
- 迪米特
一個物件應該對其它物件保持最少的瞭解
類與類關係越密切,耦合度越大
迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。對於被依賴的類不管多麼複雜,都儘量將邏輯封裝在類的內部,對愛只提供一個
public
方法,不對外洩露任何資訊只與直接的朋友通訊(直接的朋友,成員變數,方法引數,方法返回值中的類為直接朋友,出現在區域性變數中的類不是直接朋友)
- 合成複用
儘量使用合成/聚合方式來代替繼承
本作品採用《CC 協議》,轉載必須註明作者和本文連結