PHP設計模式之原型模式

PHP設計模式之原型模式

原型模式其實更形象的來講應該叫克隆模式。它主要的行爲是對對象進行克隆,可是又把被克隆的對象稱之爲最初的原型,因而,這個模式就這樣被命名了。說真的,從使用方式來看真的感受叫克隆模式更貼切一些。php

Gof類圖及解釋

GoF定義:用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象git

GoF類圖github

原型模式

代碼實現windows

abstract class Prototype {
    public $v = 'clone' . PHP_EOL;

    public function __construct() {
        echo 'create' . PHP_EOL;
    }

    abstract public function __clone();
}
複製代碼

首先咱們經過模擬的方式定義了一個原型,這裏主要是模擬了__clone()這個方法。其實這是PHP自帶的一個魔術方法,根本是不須要咱們去進行定義的,只須要在原型類中進行實現就能夠了。當外部使用clone關鍵字進行對象克隆時,直接就會進入這個魔術方法中。在這個魔術方法裏面咱們能夠對屬性進行處理,特別是針對引用屬性進行一些獨特的處理。在這個例子中,咱們只使用了一個值類型的變量。沒法體現出引用類型的問題,咱們將在後面的實例中演示對引用類型變量的處理。設計模式

class ConcretePrototype1 extends Prototype {
    public function __clone() {
    }
}

class ConcretePrototype2 extends Prototype {
    public function __clone() {
    }
}
複製代碼

模擬的具體實現的原型,其實就是主要去具體的實現__clone()方法。後面咱們看具體的例子時再說明。數組

class Client {
    public function operation() {
        $p1 = new ConcretePrototype1();
        $p2 = clone $p1;

        echo $p1->v;
        echo $p2->v;
    }
}

$c = new Client();
$c->operation();
複製代碼

客戶端使用clone來複制p1,能夠看到p2也具備相同的$v屬性。微信

  • 原型模式看似就是複製了一個相同的對象,可是請注意,複製的時候,__construct()方法並無被調用,也就是當你運行這段代碼的時候,create只輸出了一次。這也就帶出了原型模式最大的一個特色——減小建立對象時的開銷
  • 基於上述特色,咱們能夠快速的複製大量相同的對象,好比要給一個數組中塞入大量相同的對象時。
  • 複製出來的對象中若是都是值類型的屬性,咱們能夠任意修改,不會對原型產生影響。而若是有引用類型的變量,則須要在__clone()方法進行一些處理,不然修改了複製對象的引用變量中的內容,會對原型對象中的內容有影響。

咱們的手機操做系統(也能夠想象一下PC電腦的操做系統),都是怎樣安裝到設備中呢?其實都是不停的複製拷貝最初的那一套系統。用微軟的例子很是好說明這個問題,當年微軟可以成爲一個帝國,其實也是由於他不停的將winodws操做系統拷貝複製到光盤中,而後賣給千家萬戶(固然,這裏沒中國什麼事兒)。而中國市場呢,大量的高手破解了windows以後也是由這一份文件不停的複製拷貝才裝到了咱們的電腦中。手機、智能設備等各種產品的操做系統、軟件都是如此。一次開發無限拷貝正是軟件行業暴利的緣由。畢竟咱們的系統也是由很多的工程師日以繼夜的996在Android原生系統的基礎上開發出來的,趕忙不斷的複製到即將出廠的手機上吧!!學習

完整代碼:github.com/zhangyue050…this

實例

一樣仍是拿手機來講事兒,此次咱們是根據不一樣的運營商須要去開發一批定製機,也就是套餐機。這批手機說實話都並無什麼不一樣,大部分都是相同的配置,可是運營商系統不一樣,並且偶爾有一些型號的CPU和內存也可能存在不一樣。這個時候,咱們就能夠用原型模式來進行快速的複製而且只修改一部分不相同的地方啦。spa

原型模式生產手機類圖

原型模式生產手機

完整源碼:github.com/zhangyue050…

<?php
interface ServiceProvicer {
    public function getSystem();
}

class ChinaMobile implements ServiceProvicer {
    public $system;
    public function getSystem(){
        return "中國移動" . $this->system;
    }
}
class ChinaUnicom implements ServiceProvicer {
    public $system;
    public function getSystem(){
        return "中國聯通" . $this->system;
    }
}

class Phone {
    public $service_province;
    public $cpu;
    public $rom;
}

class CMPhone extends Phone {
    function __clone() {
        // $this->service_province = new ChinaMobile();
    }
}

class CUPhone extends Phone {
    function __clone() {
        $this->service_province = new ChinaUnicom();
    }
}


$cmPhone = new CMPhone();
$cmPhone->cpu = "1.4G";
$cmPhone->rom = "64G";
$cmPhone->service_province = new ChinaMobile();
$cmPhone->service_province->system = 'TD-CDMA';
$cmPhone1 = clone $cmPhone;
$cmPhone1->service_province->system = 'TD-CDMA1';

var_dump($cmPhone);
var_dump($cmPhone1);
echo $cmPhone->service_province->getSystem();
echo $cmPhone1->service_province->getSystem();


$cuPhone = new CUPhone();
$cuPhone->cpu = "1.4G";
$cuPhone->rom = "64G";
$cuPhone->service_province = new ChinaUnicom();
$cuPhone->service_province->system = 'WCDMA';
$cuPhone1 = clone $cuPhone;
$cuPhone1->rom = "128G";
$cuPhone1->service_province->system = 'WCDMA1';

var_dump($cuPhone);
var_dump($cuPhone1);
echo $cuPhone->service_province->getSystem();
echo $cuPhone1->service_province->getSystem();

複製代碼

說明

  • 打印了不少東西呀,不過主要的仍是看看移動手機,也就是CMPhone中的__clone()方法,咱們沒有從新去初始化一個新對象。這時,複製的cmPhone1對象中的service_province和cmPhone中的是同一個對象。沒錯,這就是引用的複製問題。引用只是複製了引用的地址,他們指向的是同一個對象。當cmPhone1修改service_province對象裏面的屬性內容時,cmPhone裏面的service_province對象裏面的屬性也跟着改變了。
  • 在CUPhone中,咱們從新new了一個新的service_province對象。此次外面的cuPhone1對該對象中的屬性修改時就不會影響cuPhone中引用對象的值。
  • 原型模式中最主要的就是要注意上述兩點,而普通的值屬性會直接進行復制,不會產生這個問題。這裏又牽涉出另外兩個概念:淺複製深複製
  • 淺複製,是指被複制對象的全部變量都含有與原來對象相同的值,而全部的對其餘對象的引用都仍然指向原來的對象
  • 深複製把引用對象的變量指向複製過的新對象,而不是原有的被引用的對象
  • 關於引用和值的問題,咱們將在其餘的文章中進行講解,請關注微信或掘金號

下期看點

原型模式雖然日常用得很少,可是學習以後發現還真是挺有用的,特別是須要大量的重複對象時,能夠大大節約新建對象的資源需求,之後仍是須要多多練習早日應用在實際的業務場景中。下一個又會是誰呢?別急別急,先去下個館子,廚師、服務員、顧客,這三個要素就能組成一個神奇的模式:命令模式

相關文章
相關標籤/搜索