建立型設計模式 :javascript
抽象工廠模式就比如工廠方法模式的升級版,因此本文把工廠方法模式和抽象工廠模式混在一塊兒講了php
單例模式(Singleton Pattern):顧名思義,就是隻有一個實例。做爲對象的建立模式,單例模式確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。java
(一)爲何要使用PHP單例模式web
1,開發中有些時候,一個應用中會存在大量的數據庫操做。 在使用面向對象的方式開發時, 若是使用單例模式,則能夠避免大量的new 操做消耗的資源,還能夠減小數據庫鏈接這樣就不容易出現 too many connections
狀況。數據庫
2,若是系統中須要有一個類來全局控制某些配置信息, 那麼使用單例模式能夠很方便的實現. 這個能夠參看zend Framework的FrontController部分。設計模式
3,在一次頁面請求中, 便於進行調試, 由於全部的代碼(例如數據庫操做類db)都集中在一個類中, 咱們能夠在類中設置鉤子函數, 輸出日誌,從而避免處處var_dump, echo數組
(二)單例模式結構圖微信
(三)單例模式的實現ide
1,私有化一個屬性用於存放惟一的一個實例函數
2,私有化構造方法,私有化克隆方法,用來建立並只容許建立一個實例
3,公有化靜態方法,用於向系統提供這個實例
(四)代碼實現
class Singleton{
//存放實例
private static $_instance = null;
//私有化構造方法、
private function __construct(){
echo "單例模式的實例被構造了";
}
//私有化克隆方法
private function __clone(){
}
//公有化獲取實例方法
public static function getInstance(){
if (!(self::$_instance instanceof Singleton)){
self::$_instance = new Singleton();
}
return self::$_instance;
}
}
$singleton=Singleton::getInstance();複製代碼
優勢:由於靜態方法能夠在全局範圍內被訪問,當咱們須要一個單例模式的對象時,只需調用getInstance方法,獲取先前實例化的對象,無需從新實例化。
(五)使用Trait關鍵字實現相似於繼承單例類的功能
Trait
是PHP5.4後加入到特性,有些書說是爲了實現相似C++多重繼承的功能。Trait定義的代碼結構和類很類似。又有點像實現邏輯代碼的接口。當使用use
的時候,Trait的代碼就好像拷貝到use所在的位置取代use。從這個角度來看,更像C的宏定義
。
Trait Singleton{
//存放實例
private static $_instance = null;
//私有化克隆方法
private function __clone(){
}
//公有化獲取實例方法
public static function getInstance(){
$class = __CLASS__;
if (!(self::$_instance instanceof $class)){
self::$_instance = new $class();
}
return self::$_instance;
}
}
class DB {
private function __construct(){
echo __CLASS__.PHP_EOL;
}
}
class DBhandle extends DB {
use Singleton;
private function __construct(){
echo "單例模式的實例被構造了";
}
}
$handle=DBhandle::getInstance();
//注意若父類方法爲public,則子類只能爲pubic,
//若父類爲private,子類爲public ,protected,private均可以。複製代碼
補充:大多數書籍介紹單例模式,都會講三私一公,公有化靜態方法做爲提供對象的接口,私有屬性用於存放惟一一個單例對象。私有化構造方法,私有化克隆方法保證只存在一個單例。
但實際上,雖然咱們沒法經過new
關鍵字和clone
出一個新的對象,但咱們若想獲得一個新對象。仍是有辦法的,那就是經過序列化和反序列化
獲得一個對象。私有化__sleep()
和__wakeup()
方法依然沒法阻止經過這種方法獲得一個新對象。或許真得要阻止,你只能去__wakeup添加刪除一個實例的代碼,保證反序列化增長一個對象,你就刪除一個。不過這樣貌似有點怪異。
單例模式也細分爲懶漢模式
和餓漢模式
,感興趣的朋友能夠去了解一下。上面的代碼實現是懶漢模式
工廠模式(Factor Pattern),就是負責生成其餘對象的類或方法,也叫工廠方法模式
抽象工廠模式( Abstract Factor Pattern),可簡單理解爲工廠模式的升級版
(一)爲何須要工廠模式
1,工廠模式能夠將對象的生產從直接new 一個對象,改爲經過調用一個工廠方法生產。這樣的封裝,代碼若需修改new的對象時,不需修改多處new語句,只需更改生產對象方法。
2,若所需實例化的對象可選擇來自不一樣的類,可省略if-else多層判斷,給工廠方法傳入對應的參數,利用多態性,實例化對應的類。
(二)工廠模式結構圖
1,工廠方法模式
2,抽象工廠模式
(三)簡單實現代碼
//工廠類
class Factor{
//生成對象方法
static function createDB(){
echo '我生產了一個DB實例';
return new DB;
}
}
//數據類
class DB{
public function __construct(){
echo __CLASS__.PHP_EOL;
}
}
$db=Factor::createDB();複製代碼
(四)實現一個運算器
//抽象運算類
abstract class Operation{
abstract public function getVal($i,$j);//抽象方法不能包含方法體 } //加法類 class OperationAdd extends Operation{
public function getVal($i,$j){
return $i+$j;
}
}
//減法類
class OperationSub extends Operation{
public function getVal($i,$j){
return $i-$j;
}
}
//計數器工廠
class CounterFactor {
private static $operation;
//工廠生產特定類對象方法
static function createOperation(string $operation){
switch($operation){
case '+' : self::$operation = new OperationAdd;
break;
case '-' : self::$operation = new OperationSub;
break;
}
return self::$operation;
}
}
$counter = CounterFactor::createOperation('+');
echo $counter->getVal(1,2);複製代碼
缺點:如果再增長一個乘法運算,除了增長一個乘法運算類以外,還得去工廠生產方法裏面添加對應的case代碼,違反了開放-封閉原則。
解決方法(1):經過傳入指定類名
//計算器工廠
class CounterFactor {
//工廠生產特定類對象方法
static function createOperation(string $operation){
return new $operation;
}
}
class OperationMul extends Operation{
public function getVal($i,$j){
return $i*$j;
}
}
$counter = CounterFactor::createOperation('OperationMul');複製代碼
解決方法(2):經過抽象工廠模式
這裏順帶提一個問題:若是我係統還有個生產一個文本輸入器工廠,那麼那個工廠和這個計數器工廠又有什麼關係呢。
抽象高於實現
其實咱們徹底能夠抽象出一個抽象工廠,而後將對應的對象生產交給子工廠實現。代碼以下
//抽象運算類
abstract class Operation{
abstract public function getVal($i,$j);//抽象方法不能包含方法體 } //加法類 class OperationAdd extends Operation{
public function getVal($i,$j){
return $i+$j;
}
}
//乘法類
class OperationMul extends Operation{
public function getVal($i,$j){
return $i*$j;
}
}
//抽象工廠類
abstract class Factor{
abstract static function getInstance(); } //加法器生產工廠 class AddFactor extends Factor {
//工廠生產特定類對象方法
static function getInstance(){
return new OperationAdd;
}
}
//減法器生產工廠
class MulFactor extends Factor {
static function getInstance(){
return new OperationMul;
}
}
//文本輸入器生產工廠
class TextFactor extends Factor{
static function getInstance(){}
}
$mul = MulFactor::getInstance();
echo $mul->getVal(1,2);複製代碼
建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。
建造者模式是一步一步建立一個複雜的對象,它容許用戶只經過指定複雜對象的類型和內容就能夠構建它們,用戶不須要知道內部的具體構建細節。建造者模式屬於對象建立型模式。根據中文翻譯的不一樣,建造者模式又能夠稱爲生成器模式。
(一)爲何須要建造者模式
1,對象的生產須要複雜的初始化,好比給一大堆類成員屬性賦初值,設置一下其餘的系統環境變量。使用建造者模式能夠將這些初始化工做封裝起來。
2,對象的生成時可根據初始化的順序或數據不一樣,而生成不一樣角色。
(二)建造者模式結構圖
(三)模式應用
在不少遊戲軟件中,地圖包括天空、地面、背景等組成部分,人物角色包括人體、服裝、裝備等組成部分,可使用建造者模式對其進行設計,經過不一樣的具體建造者建立不一樣類型的地圖或人物
(四)設計實例
若是咱們想創造出有一個person類,咱們經過實例化時設置的屬性不一樣,讓他們兩人一個是速度快的小孩,一個是知識深的長者
class person {
public $age;
public $speed;
public $knowledge;
}
//抽象建造者類
abstract class Builder{
public $_person;
public abstract function setAge(); public abstract function setSpeed(); public abstract function setKnowledge(); public function __construct(Person $person){
$this->_person=$person;
}
public function getPerson(){
return $this->_person;
}
}
//長者建造者
class OlderBuider extends Builder{
public function setAge(){
$this->_person->age=70;
}
public function setSpeed(){
$this->_person->speed="low";
}
public function setKnowledge(){
$this->_person->knowledge='more';
}
}
//小孩建造者
class ChildBuider extends Builder{
public function setAge(){
$this->_person->age=10;
}
public function setSpeed(){
$this->_person->speed="fast";
}
public function setKnowledge(){
$this->_person->knowledge='litte';
}
}
//建造指揮者
class Director{
private $_builder;
public function __construct(Builder $builder){
$this->_builder = $builder;
}
public function built(){
$this->_builder->setAge();
$this->_builder->setSpeed();
$this->_builder->setKnowledge();
}
}
//實例化一個長者建造者
$oldB = new OlderBuider(new Person);
//實例化一個建造指揮者
$director = new Director($oldB);
//指揮建造
$director->built();
//獲得長者
$older = $oldB->getPerson();
var_dump($older);複製代碼
(五)總結
使用建造者模式時,咱們把建立一個person實例的過程分爲了兩步.
一步是先交給對應角色的建造者,如長者建造者。這樣的好處就把角色的屬性設置封裝了起來,咱們不用在new一個person時,由於要獲得一個older角色的實例,而在外面寫了一堆$older->age=70。
另外一步是交給了一個建造指揮者,調了一個built方法,經過先設置age,再設置Speed的順序,初始化這個角色。固然在這個例子中,初始化的順序,是無所謂的。可是若是對於一個建造漢堡,或是地圖,初始化的順序不一樣,可能就會獲得不一樣的結果。
也許,你會說,我直接設置也很方便呀。是的,對於某些狀況是這樣的。可是若是你考慮,我如今想增長一個青年人角色呢?若是我如今想讓建造有初始化有三種不一樣的順序呢?
若是你使用了建造者模式,這兩個問題就簡單了,增長一個青年人角色,那就增長一個青年年建造者類。初始化三種不一樣的順序,那麼就在指揮建造者中增長兩種建造方法。
原型模式(Prototype Pattern):與工廠模式相似,都是用來建立對象的。利用克隆來生成一個大對象,減小建立時的初始化等操做佔用開銷
(一)爲何須要原型模式
1,有些時候,咱們須要建立多個相似的大對象。若是直接經過new對象,開銷很大,並且new完還得進行重複的初始化工做。可能把初始化工做封裝起來的,可是對於系統來講,你封不封裝,初始化工做仍是要執行。,
2,原型模式則不一樣,原型模式是先建立好一個原型對象,而後經過clone這個原型對象來建立新的對象,這樣就免去了重複的初始化工做,系統僅需內存拷貝便可。
(二)原型模式結構圖
(三)簡單實例
若是說,咱們如今正開發一個遊戲,有不一樣的地圖,地圖大小都是同樣的,而且都有海洋,可是不一樣的地圖溫度不同。
<?php
//抽象原型類
Abstract class Prototype{
abstract function __clone(); } //具體原型類 class Map extends Prototype{
public $width;
public $height;
public $sea;
public function setAttribute(array $attributes){
foreach($attributes as $key => $val){
$this->$key = $val;
}
}
public function __clone(){}
}
//海洋類.這裏就不具體實現了。
class Sea{}
//使用原型模式建立對象方法以下
//先建立一個原型對象
$map_prototype = new Map;
$attributes = array('width'=>40,'height'=>60,'sea'=>(new Sea));
$map_prototype->setAttribute($attributes);
//如今已經建立好原型對象了。若是咱們要建立一個新的map對象只須要克隆一下
$new_map = clone $map_prototype;
var_dump($map_prototype);
var_dump($new_map);複製代碼
經過上面的代碼,咱們能夠發現利用原型模式,只須要實例化並初始化一個地圖原型對象。之後生產一個地圖對象,均可以直接經過clone原型對象產生。省去了從新初始化的過程。
可是上面的代碼仍是存在一些問題。那就是它只是一個淺拷貝
,什麼意思呢?map原型對象有一個屬性sea存放了一個sea對象,在調用setAttribute的時候,對象的賦值方式默認是引用。而當咱們克隆map對象時,直接克隆了map的sea屬性,這就使得克隆出來的對象與原型對象的sea屬性對指向了,同一個sea對象的內存空間。若是這個時候,咱們改變了克隆對象的sea屬性,那麼原型對象的sea屬性也跟着改變。
這顯然是不合理的,咱們想要的結果應該是深拷貝
,也就是改變克隆對象的全部屬性,包括用來存放sea這種其餘對象的屬性時,也不影響原型對象。
固然,講到這裏你能夠當我在胡說。但我仍是建議你打印下原型對象和克隆對象,看一下他們的sea屬性吧,而後去好好了解一下什麼叫深拷貝
和淺拷貝
。
(三)深拷貝的實現
深拷貝的實現,其實也簡單,咱們只要實現Map類的克隆方法就好了。這就是咱們爲何要定義一個抽象原型類的緣由。咱們利用抽象類,強制全部繼承的具體原型類都必須來實現這個克隆方法。改進以下:
//具體原型類
class Map extends Prototype{
public $width;
public $height;
public $sea;
public function setAttribute(array $attributes){
foreach($attributes as $key => $val){
$this->$key = $val;
}
}
//實現克隆方法,用來實現深拷貝
public function __clone(){
$this->sea = clone $this->sea;
}
}複製代碼
到這裏原型模式就算實現了,可是我覺還能夠進一步進行封裝,利用工廠模式或建造者模式的思想。
(四)延伸
舉個例子,若是咱們在克隆這個地圖對象的同時咱們還須要進行一下系統設置,或是說咱們想給原型對象的clone_id屬性賦值當前已經拷貝了多少個對象的總數量?
咱們能夠把clone這個動做封裝到一個相似的工廠方法裏面去,簡單地實現一下,雖然不咋嚴謹。
<?php
//抽象原型類
Abstract class Prototype{
abstract function __clone(); } //具體原型類 class Map extends Prototype{
public $clone_id=0;
public $width;
public $height;
public $sea;
public function setAttribute(array $attributes){
foreach($attributes as $key => $val){
$this->$key = $val;
}
}
//實現克隆方法,用來實現深拷貝
public function __clone(){
$this->sea = clone $this->sea;
}
}
//海洋類.這裏就不具體實現了。
class Sea{}
//克隆機器
class CloneTool{
static function clone($instance,$id){
$instance->clone_id ++;
system_write(get_class($instance));
return clone $instance;
}
}
//系統通知函數
function system_write($class){
echo "有人使用克隆機器克隆了一個{$class}對象".PHP_EOL;
}
//使用原型模式建立對象方法以下
//先建立一個原型對象
$map_prototype = new Map;
$attributes = array('width'=>40,'height'=>60,'sea'=>(new Sea));
$map_prototype->setAttribute($attributes);
//如今已經建立好原型對象了。若是咱們要建立一個新的map對象只須要克隆一下
$new_map = CloneTool::clone($map_prototype,1);
var_dump($map_prototype);
var_dump($new_map);複製代碼
(五)模型應用
多用於建立大對象,或初始化繁瑣的對象。如遊戲中的背景,地圖。web中的畫布等等
單例模式,工廠模式,建造者模式,原型模式都屬於建立型模式
。使用建立型模式的目的,就是爲了建立一個對象。
建立型模式的優勢,在於如何把複雜的建立過程封裝起來,如何下降系統的內銷。
我認爲建立型模式的一個重要的思想其實就是封裝
,利用封裝,把直接得到一個對象改成經過一個接口得到一個對象。這樣最明顯的優勢,在於咱們能夠把一些複雜的操做也封裝到接口裏去,咱們使用時直接調這個接口就能夠了。具體的實現,咱們在主線程序中就再也不考慮。這樣使得咱們的代碼看上去更少,更簡潔。
單例模式
,咱們把對象的生成從new改成經過一個靜態方法,經過靜態方法的控制,使得咱們老是返回同一個實例給調用者,確保了系統只有一個實例
工廠模式
,也是同樣,生成對象改成接口,還能夠經過傳參實例化不一樣的類。若是咱們經過直接new的話,那麼咱們在主線代碼中少不了要寫if condition new 一個加法類,else new一個減法類。封裝了以後,咱們經過接口傳參,還能利用多態的特性去替代if else語句。
並且咱們遵循了單一原則,讓類的功能單一。咱們若是須要一個新功能,只需添加一個類,不用修改其餘類的功能。這樣使得代碼的擴展性更好了。
建造者模式
,咱們把初始化的工做和順序,封裝給了一個建造者和指揮者。若是,咱們下次要建造的類屬性,或是順序不一樣。咱們只需新建對應的建造者類或添加對應的指揮者方法,沒必要再去修改原代碼。並且咱們也省去了,這new對象後,還要寫$attribut=array();這種一大串數組,而後調好幾個方法去初始化的工做。
原型模式
,經過先建立一個原型對象,而後直接克隆,省去了new大對象帶來的開銷浪費。固然咱們一樣能夠經過,封裝clone這個動做。使得咱們在clone的同時還能夠作一些其餘的準備工做。
感謝閱讀,因爲筆者也是初學設計模式,能力有限,文章不可避免地有失偏頗
後續更新 PHP設計模式-結構型設計模式 介紹,歡迎你們評論指正
我最近的學習總結: