php設計模式(一):簡介及建立型模式
原文請見:
可以線上執行檢視效果哦!
我們分三篇文章來總結一下設計模式在PHP中的應用,這是第一篇建立型模式。
一、設計模式簡介
首先我們來認識一下什麼是設計模式:
設計模式是一套被反覆使用、容易被他人理解的、可靠的程式碼設計經驗的總結。
設計模式不是Java的專利,我們用面向物件的方法在PHP裡也能很好的使用23種設計模式。
那麼我們常說的架構、框架和設計模式有什麼關係呢?
架構是一套體系結構,是專案的整體解決方案;框架是可供複用的半成品軟體,是具體程式程式碼。架構一般會涉及到採用什麼樣的框架來加速和優化某部分問題的解決,而好的框架程式碼裡合理使用了很多設計模式。
二、提煉設計模式的幾個原則:
開閉原則:模組應對擴充套件開放,而對修改關閉。
里氏代換原則:如果呼叫的是父類的話,那麼換成子類也完全可以執行。
依賴倒轉原則:抽象不依賴細節,面向介面程式設計,傳遞引數儘量引用層次高的類。
介面隔離原則:每一個介面只負責一種角色。
合成/聚合複用原則:要儘量使用合成/聚合,不要濫用繼承。
三、設計模式的功用?
1、設計模式能解決
替換雜亂無章的程式碼,形成良好的程式碼風格
程式碼易讀,工程師們都能很容易理解
增加新功能時不用修改介面,可擴充套件性強
穩定性好,一般不會出現未知的問題
2、 設計模式不能解決:
設計模式是用來組織你的程式碼的模板,而不是直接呼叫的庫;
設計模式並非最高效,但是程式碼的可讀性和可維護性更重要;
不要一味追求並套用設計模式,重構時多考慮;
四、設計模式分類
1、建立型模式:
單例模式、工廠模式(簡單工廠、工廠方法、抽象工廠)、建立者模式、原型模式。
2、結構型模式:
介面卡模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
3、行為型模式:
模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、直譯器模式、狀態模式、策略模式、職責鏈模式、訪問者模式。
五、建立型設計模式
1、單例模式
目的:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
應用場景:資料庫連線、快取操作、分散式儲存。
<?php /** * 優才網公開課示例程式碼 * * 單例模式 * * @author 優才網全棧工程師教研組 * @see http://www.ucai.cn */ class DbConn { private static $_instance = null; protected static $_counter = 0; protected $_db; //私有化建構函式,不允許外部建立例項 private function __construct() { self::$_counter += 1; } public function getInstance() { if (self::$_instance == null) { self::$_instance = new DbConn(); } return self::$_instance; } public function connect() { echo "connected: ".(self::$_counter)."\n"; return $this->_db; } } /* * 不使用單例模式時,刪除建構函式的private後再測試,第二次呼叫建構函式後,_counter變成2 */ // $conn = new DbConn(); // $conn->connect(); // $conn = new DbConn(); // $conn->connect(); //使用單例模式後不能直接new物件,必須呼叫getInstance獲取 $conn = DbConn::getInstance(); $db = $conn->connect(); //第二次呼叫是同一個例項,_counter還是1 $conn = DbConn::getInstance(); $db = $conn->connect(); ?>
特別說明:這裡getInstance裡有if判斷然後再生成物件,在多執行緒語言裡是會有併發問題的。例如java的解決方案有二個,給方法加上synchronized關鍵詞變成同步,或者把_instanc的初始化提前放到類成員變數定義時,但是這2種方式php都不支援。不過因為php不支援多執行緒所以不需要考慮這個問題了。
2、工廠模式
實現:定義一個用於建立物件的介面,讓子類決定例項化哪一個類。
應用場景:眾多子類並且會擴充、建立方法比較複雜。
<?php
/**
* 優才網公開課示例程式碼
*
* 工廠模式
*
* @author 優才網全棧工程師教研組
* @see http://www.ucai.cn
*/
//抽象產品
interface Person {
public function getName();
}
//具體產品實現
class Teacher implements Person {
function getName() {
return "老師\n";
}
}
class Student implements Person {
function getName() {
return "學生\n";
}
}
//簡單工廠
class SimpleFactory {
public static function getPerson($type) {
$person = null;
if ($type == 'teacher') {
$person = new Teacher();
} elseif ($type == 'student') {
$person = new Student();
}
return $person;
}
}
//簡單工廠呼叫
class SimpleClient {
function main() {
// 如果不用工廠模式,則需要提前指定具體類
// $person = new Teacher();
// echo $person->getName();
// $person = new Student();
// echo $person->getName();
// 用工廠模式,則不需要知道物件由什麼類產生,交給工廠去決定
$person = SimpleFactory::getPerson('teacher');
echo $person->getName();
$person = SimpleFactory::getPerson('student');
echo $person->getName();
}
}
//工廠方法
interface CommFactory {
public function getPerson();
}
//具體工廠實現
class StudentFactory implements CommFactory {
function getPerson(){
return new Student();
}
}
class TeacherFactory implements CommFactory {
function getPerson() {
return new Teacher();
}
}
//工廠方法呼叫
class CommClient {
static function main() {
$factory = new TeacherFactory();
echo $factory->getPerson()->getName();
$factory = new StudentFactory();
echo $factory->getPerson()->getName();
}
}
//抽象工廠模式另一條產品線
interface Grade {
function getYear();
}
//另一條產品線的具體產品
class Grade1 implements Grade {
public function getYear() {
return '2003級';
}
}
class Grade2 implements Grade {
public function getYear() {
return '2004級';
}
}
//抽象工廠
interface AbstractFactory {
function getPerson();
function getGrade();
}
//具體工廠可以產生每個產品線的產品
class Grade1TeacherFactory implements AbstractFactory {
public function getPerson() {
return new Teacher();
}
public function getGrade() {
return new Grade1();
}
}
class Grade1StudentFactory implements AbstractFactory {
public function getPerson() {
return new Student();
}
public function getGrade() {
return new Grade1();
}
}
class Grade2TeacherFactory implements AbstractFactory {
public function getPerson() {
return new Teacher();
}
public function getGrade() {
return new Grade2();
}
}
//抽象工廠呼叫
class FactoryClient {
function printInfo($factory) {
echo $factory->getGrade()->getYear().$factory->getPerson()->getName();
}
function main() {
$client = new FactoryClient();
$factory = new Grade1TeacherFactory();
$client->printInfo($factory);
$factory = new Grade1StudentFactory();
$client->printInfo($factory);
$factory = new Grade2TeacherFactory();
$client->printInfo($factory);
}
}
//簡單工廠
//SimpleClient::main();
//工廠方法
//CommClient::main();
//抽象工廠
FactoryClient::main();
?>
三種工廠的區別是,抽象工廠由多條產品線,而工廠方法只有一條產品線,是抽象工廠的簡化。而工廠方法和簡單工廠相對,大家初看起來好像工廠方法增加了許多程式碼但是實現的功能和簡單工廠一樣。但本質是,簡單工廠並未嚴格遵循設計模式的開閉原則,當需要增加新產品時也需要修改工廠程式碼。但是工廠方法則嚴格遵守開閉原則,模式只負責抽象工廠介面,具體工廠交給客戶去擴充套件。在分工時,核心工程師負責抽象工廠和抽象產品的定義,業務工程師負責具體工廠和具體產品的實現。只要抽象層設計的好,框架就是非常穩定的。
3、建立者模式
在建立者模式中,客戶端不再負責物件的建立與組裝,而是把這個物件建立的責任交給其具體的建立者類,把組裝的責任交給組裝類,客戶端支付對物件的呼叫,從而明確了各個類的職責。
應用場景:建立非常複雜,分步驟組裝起來。
<?php
/**
* 優才網公開課示例程式碼
*
* 建立者模式
*
* @author 優才網全棧工程師教研組
* @see http://www.ucai.cn
*/
//購物車
class ShoppingCart {
//選中的商品
private $_goods = array();
//使用的優惠券
private $_tickets = array();
public function addGoods($goods) {
$this->_goods[] = $goods;
}
public function addTicket($ticket) {
$this->_tickets[] = $ticket;
}
public function printInfo() {
printf("goods:%s, tickets:%s\n", implode(',', $this->_goods), implode(',', $this->_tickets));
}
}
//假如我們要還原購物車的東西,比如使用者關閉瀏覽器後再開啟時會根據cookie還原
$data = array(
'goods' => array('衣服', '鞋子'),
'tickets' => array('減10'),
);
//如果不使用建立者模式,則需要業務類裡一步步還原購物車
// $cart = new ShoppingCart();
// foreach ($data['goods'] as $goods) {
// $cart->addGoods($goods);
// }
// foreach ($data['tickets'] as $ticket) {
// $cart->addTicket($ticket);
// }
// $cart->printInfo();
// exit;
//我們提供建立者類來封裝購物車的資料組裝
class CardBuilder {
private $_card;
function __construct($card) {
$this->_card = $card;
}
function build($data) {
foreach ($data['goods'] as $goods) {
$this->_card->addGoods($goods);
}
foreach ($data['tickets'] as $ticket) {
$this->_card->addTicket($ticket);
}
}
function getCrad() {
return $this->_card;
}
}
$cart = new ShoppingCart();
$builder = new CardBuilder($cart);
$builder->build($data);
echo "after builder:\n";
$cart->printInfo();
?>
可以看出,使用建立者模式對內部資料複雜的物件封裝資料組裝過程後,對外介面就會非常簡單和規範,增加修改新資料項也不會對外部造成任何影響。
3、 原型模式
用原型例項指定建立物件的種類,並且通過拷貝這個原型來建立新的物件。
應用場景: 類的資源非常多、效能和安全要求,一般和工廠方法結合使用。
<?php
/**
* 優才網公開課示例程式碼
*
* 原型模式
*
* @author 優才網全棧工程師教研組
* @see http://www.ucai.cn
*/
//宣告一個克隆自身的介面
interface Prototype {
function copy();
}
//產品要實現克隆自身的操作
class Student implements Prototype {
//簡單起見,這裡沒有使用get set
public $school;
public $major;
public $name;
public function __construct($school, $major, $name) {
$this->school = $school;
$this->major = $major;
$this->name = $name;
}
public function printInfo() {
printf("%s,%s,%s\n", $this->school, $this->major, $this->name);
}
public function copy() {
return clone $this;
}
}
$stu1 = new Student('清華大學', '計算機', '張三');
$stu1->printInfo();
$stu2 = $stu1->copy();
$stu2->name = '李四';
$stu2->printInfo();
?>
這裡可以看到,如果類的成員變數非常多,如果由外部建立多個新物件再一個個賦值,則效率不高程式碼冗餘也容易出錯,通過原型拷貝複製自身再進行微小修改就是另一個新物件了。
設計模式的第一部分,建立型模式就總結完了。下面還有兩部分結構型設計模式和行為型設計模式稍後繼續。