什麼是設計模式
在軟體開發過程中,經常出現的經典場景的典型解決方案,稱為設計模式
如何學習設計模式
典型場景 --> 典型問題 --> 典型解決辦法
多型
用來消除邏輯語句.
多型(ploymorphism)是一個生物學上的概念,指同一特種的多種表現形態.
在物件導向中,指某種物件例項的不同表現形態.
<?php
abstract class Tiger {
public abstract function climb();
}
class XTiger extends Tiger {
public function climb() {
echo 'Drop' , '<br/>';
}
}
class MTiger extends Tiger {
public function climb() {
echo 'Up' , '<br/>';
}
}
class Cat {
public function climb() {
echo 'Fly';
}
}
class Client {
public static function call(Tiger $animal) { // 引數限定不嚴格,可以更加靈活, 可以傳遞一個父類型別,就可以有不同的子類形態
$animal->climb();
}
}
Client::call(new XTiger());
Client::call(new MTiger());
Client::call(new Cat());
?>
在23種設計模式中,可以有些模式可以自然消除的.
面向介面開發
減少new
的操作.
熟悉概念:
呼叫端
的概念客戶端(Client)
的概念
例如:在Java中,寫了一堆類,最終打成一個包。呼叫端可以把包引入,然後使用類中定義的方法,在開發過程中可以使用檢視裡面的方法。
假設:開發中引入的是sdk
,都不讓看。如何呼叫,必須開放一些介面。存在面向介面開發
介面:共同的規則
面向介面開發
<?php
// 共同介面
// 服務端打包
interface DB {
function conn();
}
// 服務端開發(不知道將會被誰呼叫)
class DbMysql implements DB {
public function conn() {
echo 'conn mysql <br/>';
}
}
class DbSqlite implements DB {
public function conn() {
echo 'conn sqlite <br/>';
}
}
// ***客戶端**** (看不到DbMysql,DbSqlite的內部實現細節)
// 只知道,上述兩個類實現了db介面
$db = new DbMysql();
$db->conn();
$db = new DbSqlite();
$db->conn();
// 無論是開發者,還是呼叫者都是為介面負責
簡單工廠模式
模式的作用:
發生連線的雙方,知道的越少越好.
在物件導向設計法則中,重要的開閉原則:對於修改是封閉的,對於擴充套件是開放的.
<?php
// 共同介面
// 服務端打包
interface DB {
function conn();
}
// 服務端開發(不知道將會被誰呼叫)
class DbMysql implements DB {
public function conn() {
echo 'conn mysql <br/>';
}
}
class DbSqlite implements DB {
public function conn() {
echo 'conn sqlite <br/>';
}
}
// 簡單工廠
class Factory {
public static function createDB($type) {
if ($type == 'mysql') {
return new DbMysql();
} else if ($type == 'sqlite') {
return new DbSqlite();
} else {
throw new Exception('Error db type', 1);
}
}
}
// 客戶端不知道服務端到底有哪些類名,
// 只知道對方開放了一個Factory::createDB方法.
// 靜態方法允許傳遞資料庫名稱
$msqyl = Factory::createDB('mysql');
$msqyl->conn();
$sqlit = Factory::createDB('sqlite');
$sqlit->conn();
// 如果增加oracle型別,怎麼辦?
// 服務端要修改Factory的內容(在Java,C++中,改完之後還得再編譯)
// 在物件導向設計法則中,重要的開閉原則 --- 對於修改是封閉的,對於擴充套件是開放的.
// 需求可以擴充套件子類來實現。
工廠方法
進行擴充套件,避免對原有資料進行修改,只需要新增程式碼的子類,就可以完成。
對於修改是封閉的,對於擴充套件是開放的.
<?php
// 共同介面
// 資料庫的介面
interface DB {
function conn();
}
// 創造資料庫的介面
interface Factory {
function createDB();
}
// 服務端開發(不知道將會被誰呼叫)
class DbMysql implements DB {
public function conn() {
echo 'conn mysql <br/>';
}
}
class DbSqlite implements DB {
public function conn() {
echo 'conn sqlite <br/>';
}
}
class MySqlFactory implements Factory {
public function createDB() {
return new DbMysql();
}
}
class SqliteFactory implements Factory {
public function createDB() {
return new DbSqlite();
}
}
// ==== 伺服器端新增oracle類
// 進行擴充套件,避免對原有資料進行修改
class orcale implements DB {
public function conn() {
echo 'conn orcal <br />';
}
}
class orcaleFactory implements Factory {
public function createDB() {
return new orcale();
}
}
// ------客戶端開始呼叫.
$fact = new MysqlFactory();
$db = $fact->createDB();
$db->conn();
$fact = new SqliteFactory();
$db = $fact->createDB();
$db->conn();
單例模式
常見使用場景:
需要資料庫類的時候
操作cookie類
上傳圖片類
DB.class.php
Upload.class.php
Cookie.class.php
// 這三個類都需要讀取配置檔案資訊,而配置檔案是共用的,因此配置讀取類有一個物件就夠了。
// (如何保證物件只有一個)
PHP物件什麼時候全等
二個物件是一個的時候.
單例模式實現
封閉外部new操作
內部開公共介面,負責new操作,控制單一例項
禁止繼承覆蓋
__construcotr
防止克隆
<?php
/**
單例模式
*/
// 第一步,普通類
// class Single {
// }
// $s1 = new Single();
// $s2 = new Single();
// var_dump($s1, $s2);
// var_dump($s1 == $s2);
// ---------------------
// 第二步,封鎖new操作
// class Single {
// // 在new 的時候會觸發魔術函式,__constructor,可以在 __constructor 魔術函式中做操作
// protected function __constructor() { // 把__constructor()魔術方法保護起來, 導致的後果,一個物件都沒法new。(大門關上了,需要開窗)
// }
// }
// $s1 = new Single();
// ---------------------
// 第三步,留介面來new物件
// class Single {
// public static function getIns() { // getIns的控制權在class內部,可以在getIns做手腳
// return new self(); // 返回自身例項
// }
// protected function __constructor() {
// }
// }
// $s1 = Single::getIns();
// $s2 = Single::getIns();
// var_dump($s1, $s2);
// var_dump($s1 == $s2);
// ---------------------
// 第四步,getIns要預先判斷例項
// class Single {
// protected static $ins = null;
// public static function getIns() { // getIns的控制權在class內部,可以在getIns做手腳
// if (self::$ins === null) {
// self::$ins = new self();
// }
// return self::$ins; // 返回自身例項
// }
// protected function __constructor() {
// }
// }
// $s1 = Single::getIns();
// $s2 = Single::getIns();
// var_dump($s1, $s2);
// var_dump($s1 == $s2); // true
// 問題 :繼承之後 constructor 被公開, 可以使用final
// class multi extends Single {
// public function __constructor() { // 繼承之後 constructor 被公開
// }
// }
// ---------------------
// 第五步,final,防止繼承時,被修改許可權
// class Single {
// protected static $ins = null;
// public static function getIns() { // getIns的控制權在class內部,可以在getIns做手腳
// if (self::$ins === null) {
// self::$ins = new self();
// }
// return self::$ins; // 返回自身例項
// }
// final protected function __constructor() { // 方法前加 final,則方法不能被覆蓋,類前加final,則類不能被繼承。
// }
// }
// class multi extends Single {
// public function __constructor() { // 繼承之後 constructor 被公開
// }
// }
// $s1 = Single::getIns();
// $s2 = clone $s1; // 克隆了,又產生了多個物件.
// var_dump($s1, $s2);
// var_dump($s1 === $s2); // true
// ---------------------
// 第六步,禁止clone
class Single {
protected static $ins = null;
public static function getIns() { // getIns的控制權在class內部,可以在getIns做手腳
if (self::$ins === null) {
self::$ins = new self();
}
return self::$ins; // 返回自身例項
}
final protected function __constructor() { // 方法前加 final,則方法不能被覆蓋,類前加final,則類不能被繼承。
}
// 封鎖clone
final protected function __clone() {
}
}
$s1 = Single::getIns();
$s2 = clone $s1; // 克隆了,又產生了多個物件.
var_dump($s1, $s2);
var_dump($s1 === $s2); // true
觀察者模式
一個物件變化,引起其它物件的反應。可以讓其它幾個物件觀察變化的物件的反應.
一對多的關係.
優點:解耦。
觀察者模式中的三者: Subject
, Observer
, Client
;
// Subject
attach() // 記憶多個的物件
detach() // 告知記憶的物件,變化情況
notify() // 更新通知
// Observer
update() // 更新物件中執行的邏輯
// Client
// 呼叫新增觀察者`attach()`
JavaScript實現觀察者模式:
var select = document.querySelector('select');
var content = document.querySelector('.content');
var ad = document.querySelector('.ad');
// Subject
select.observer = {};
// 新增觀察者
select.attach = function(key, obj) {
this.observer[key] = obj;
}
// 刪除觀察者
select.detach = function(key) {
delete this.observer[key];
}
// 更新通知
select.onchange = select.ontify = function() {
for (var key in this.observer) {
this.observer[key].update(this);
}
}
// Observer
// 觀察者
content.update = function(observer) { // 引數是被觀察者物件
alert('content');
if (observer.value) {
// 邏輯程式碼
}
}
ad.update = function(observer) { // 引數是被觀察者物件
alert('ad');
if (observer.value) {
// 邏輯程式碼
}
}
// Client
// 監聽
select.attach('content', content); // 只需要把獨特的表示加入 物件key中
select.attach('ad', ad);
PHP實現觀察者模式:
單一功能原則:類或者一個方法,完成一個功能即可.
PHP中內建介面:
Splsubject
Splobserver
內建類:
Splobjectstorage
<?php
// PHP實現觀察者
// PHP5中提供了觀察者(observer)和被觀察者(subject)介面
/**
被觀察者
*/
class User implements SplSubject {
public $lognum;
public $hobby;
protected $observers = null;
public function __construct($hobby) {
$this->lognum = rand(1, 10);
$this->hobby = $hobby;
$this->observers = new SplObjectStorage();
}
public function login() { // 類或者一個方法,完成一個功能即可. (單一功能原則)
// 操作session
$this->notify();
}
public function attach(SplObserver $observer) {
$this->observers->attach($observer);
}
public function detach(SplObserver $observer) {
$this->observers->detach($observer);
}
public function notify() {
$this->observers->rewind();
while($this->observers->valid()) {
$observer = $this->observers->current();
$observer->update($this);
$this->observers->next();
}
}
}
/**
觀察者
*/
class Secrity implements SplObserver {
public function update(SplSubject $subject) { // 傳入的 物件是$subject,$subject是幹什麼,隨你的意.
if($subject->lognum < 3) {
echo '這個第' . $subject->lognum . '次安全登入<br/>';
} else {
echo '這個第' . $subject->lognum . '次登入,異常<br/>';
}
}
}
class Ad implements SplObserver {
public function update(SplSubject $subject) {
if ($subject->hobby == 'sport') {
echo 'sport,nba <br/>';
} else {
echo 'good good study, day day up<br/>';
}
}
}
/**
Client
*/
$user = new User('study');
$user->attach(new Secrity());
$user->attach(new Ad());
$user->login();
職責鏈模式
也稱之為責任鏈模式(chain of resionbility)
物件產生的過程中,放在邏輯判斷中.
程式導向和麵向物件混雜在一塊,沒有充分拆開。
責任鏈模式:
權利越大的人管理越多,越嚴重的問題,越得往上級找,底層的人只能管理雞毛蒜皮的小事。
每個人都有處理事情的,權利範圍.
責任鏈模式最終都需要都一級能夠處理.
先處理最近的一級
每個物件中有職責功能
,上級
.
每個物件,儲存著對自己上級的引用,如果自己處理不了,交給上一級。
優點:使用到那一級才會new
出那一級。
<?php
header('content-type: text/html; charset=utf-8');
// 責任鏈模式
// 權利越大的人管理越多,越嚴重的問題,越得往上級找,底層的人只能管理雞毛蒜皮的小事。
// 每個人都有處理事情的,權利範圍.
class borad {
public $power = 1; // 處理範圍的許可權
protected $top = 'admin'; // 上級的範圍
public function process($lev) {
if ($lev <= $this->power) {
echo '刪除';
} else {
$top = new $this->top;
$top->process($lev);
}
}
}
class admin {
public $power = 2;
protected $top = 'police';
public function process($lev) {
if ($lev <= $this->power) {
echo '封閉';
} else {
$top = new $this->top;
$top->process($lev);
}
}
}
class police {
protected $power;
protected $top = null;
public function process() {
echo '抓!~';
}
}
$lev = 1;
$judge = new borad(); // 距離最近的一級
$judge->process($lev);
策略模式
和工廠模式相似的一種模式
<?php
/**
Strategy
策略模式
完成:加減乘除功能
*/
interface Math {
public function calc ($op1, $op2);
}
class MathAdd implements Math {
public function calc($op1, $op2) {
return $op1 + $op2;
}
}
class MathSub implements Math {
public function calc($op1, $op2) {
return $op1 - $op2;
}
}
class MathMul implements Math {
public function calc($op1, $op2) {
return $op1 * $op2;
}
}
class MathDiv implements Math {
public function calc($op1, $op2) {
if ($op2 !=0 ) return;
return $op1 / $op2;
}
}
// 一般思路: 根據`op`的值,製造物件,並且呼叫
$op = 'Add';
// 封裝一個虛擬計算器,中可以 呼叫到實際計算器
class CMath {
protected $calc = null;
public function __construct($type) {
$calc = 'Math' . $type;
$this->calc = new $calc();
}
public function calc($op1, $op2) {
return $this->calc->calc($op1, $op2);
}
}
$cmath = new CMath($op);
var_dump($cmath->calc(10, 100));
工廠方法和策略模式的區別:
工廠方法是傳遞不同引數,直接把零件(子類)進行操作。
策略模式是,真實的子類,虛擬成一個父類, 父類中操作(零件)子類。不需要直接碰子類,聚合成一個父類。
裝飾器模式
問題:繼承層次越來越多、
<?php
// 場景:釋出一篇文章
class Article {
protected $content;
public function __construct($content) {
$this->content = $content;
}
public function decorator() {
return $this->content;
}
}
$art = new Article('goods goods study, day day up <br/>');
echo $art->decorator();
// -----------------------------------------------------
// 文章需要, 需要編輯人員專門編輯
class BianArt extends article {
// 重新加工對decorator
public function summary() {
return $this->content . '編輯摘要 <br/>';
}
}
$art = new BianArt('goods goods study, day day');
echo $art->summary();
// -----------------------------------------------------
// 文章需要, 需要做SEO
class SeoArt extends BianArt {
public function seo() {
$content = $this->summary();
return $content . 'seo <br />';
}
}
$art = new SeoArt('lz');
echo $art->seo();
// -----------------------------------------------------
// 文章需要,廣告部單獨管理
class Ad extends SeoArt {
// 層次越來越深,目的是:給文章新增各種內容
}
// 繼承層次越來越多,裝飾器模式可以解決,變成倆級繼承
使用裝飾器模式,修改多級繼承
父類負責主要邏輯,子類負責裝飾,修飾。(重寫父類的方法,裝飾之後再次返回)
<?php
// 裝飾器模式
class BaseArt {
protected $content; // 文章內容
protected $art = null; // 文章物件
public function __construct($content) {
$this->content = $content;
}
public function decorator() {
return $this->content;
}
}
// 編輯文章
class BianArt extends BaseArt {
public function __construct(BaseArt $art) {
$this->art = $art;
$this->decorator();
}
public function decorator() {
return $this->content = $this->art->decorator() . '編輯人員,文章摘要';
}
}
// SEO人員
class SeoArt extends BaseArt {
public function __construct(BaseArt $art) {
$this->art = $art;
$this->decorator();
}
public function decorator() {
return $this->content = $this->art->decorator() . 'SEO';
}
}
$art = new BaseArt('day day up');
// echo $art->decorator();
$art1 = new BianArt($art);
// echo $art1->decorator();
$art2 = new SeoArt($art1);
echo $art2->decorator();
介面卡模式
把不適用的格式或者資料型別,轉換成適用目前場景
<?php
// 介面卡模式
// 伺服器端程式碼
class tianqi {
public static function show() {
$today = array('tep' => 28, 'wind' => 7, 'sun' => 1);
return serialize($today);
}
}
// 增加一個代理,介面卡
class AdapterTainqi extends tianqi {
public static function show() {
$today = parent::show();
$today = unserialize($today);
$today = json_encode($today);
return $today;
}
}
// =====客戶端呼叫=====
$taiqni = unserialize(tianqi::show());
echo '溫度:',$taiqni['tep'], '風力:', $taiqni['wind'] , '<br/>';
// java客戶端,並不認識PHP的序列化後的字串。使用介面卡模式,轉成想通模式。
$tq = AdapterTainqi::show();
$td = json_decode($tq);
echo '溫度:',$td->tep, '風力:', $td->wind;
橋接模式
在某種場景下,多個條件有共同作用,增加耦合關係,減少邏輯複雜程度
這個世界上的因素都不是單一的,都是相互耦合的.
<?php
// 橋接模式 bridge
// 功能:論壇站內給使用者傳送訊息,可以是站內簡訊,emial,手機資訊
abstract class info {
protected $send = null;
public function __construct($send) {
$this->send = $send;
}
abstract public function msg($content);
public function send($to, $content) {
$content = $this->msg($content);
$this->send->send($to, $content);
}
}
class zn {
public function send($to, $content) {
echo 'zn: ', $to, '內容是:', $content;
}
}
class email {
public function send($to, $content) {
echo 'email: ', $to, '內容是:', $content;
}
}
class sms {
public function send($to, $content) {
echo 'sms: ', $to, '內容是:', $content;
}
}
class commonInfo extends info {
public function msg($content) {
return 'common'. $content;
}
}
class warnInfo extends info {
public function msg($content) {
return 'warn'. $content;
}
}
class dangerInfo extends info {
public function msg($content) {
return 'danger'. $content;
}
}
// 站內發普通訊息
$commonInfo = new commonInfo(new zn());
$commonInfo->send('小明', '吃晚飯');
// sms發普通訊息
$commonInfo = new commonInfo(new sms());
$commonInfo->send('小紅', '吃晚飯');