PHP設計模式:類自動載入、PSR-0規範、鏈式操做、11種面向對象設計模式實現和使用、OOP的基本原則和自動加載配置

1、類自動載入

     SPL函數 (standard php librarys)php

     類自動載入,儘管 __autoload() 函數也能自動加載類和接口,但更建議使用 spl_autoload_register('函數名') 函數。 spl_autoload_register('函數名') 提供了一種更加靈活的方式來實現類的自動加載(同一個應用中,能夠支持任意數量的加載器,好比第三方庫中的)。所以,再也不建議使用 __autoload() 函數,在之後的版本中它可能被棄用。mysql

     spl_autoload_register('函數名')自動加載類,能夠重複使用,不會報函數名相同的錯誤!能夠實現咱們自定義函數的激活,它的返回值是bool類型:true or false。redis

     若是不寫參數,那麼它會去調用 spl_autoload()方法,這個方法默認會執行下面的語句:require_once 類名.php 或 類名.inc算法

spl_autoload_register('函數名') function 函數名($class){<br> require __DIR__.'/'.$class.'.php';<br> }

     咱們能夠在入口文件中,使用spl_autoload_register()來完成類的自動加載sql

2、PSR-0規範

     1. 命名空間必須與絕對路徑一致(文件裏寫命名空間從根目錄下它所在文件夾開始到它的上一層文件夾名)
     2. 類名首字母必須大寫
     3. 除入口文件外,其它的".php"文件中只能存在一個類,不能有外部可執行的代碼數據庫

3、鏈式操做

     鏈式操做最重要的是return $this
     要求每一個方法體內必須return $this編程

class Database { static private $db; private function __construct() { } static function getInstance() { if (empty(self::$db)) { self::$db = new self; return self::$db; } else { return self::$db; } } function where($where) { return $this; } function order($order) { return $this; } function limit($limit) { return $this; } function query($sql) { echo "SQL: $sql\n"; } }

鏈式操做能簡化代碼,好比:設計模式

$db=new DataBase(); $db->where("id>10")->order(2)->limit(10); 

4、魔術常量和魔術方法

4.1 魔術常量

      __FILE__ 文件的完整路徑和文件名,若是用在被包含的文件中,則返回被包含文件路徑名。
      __DIR__ 文件的所在目錄,不包括文件名。 等價於dirname(__FILE__) 除了根目錄,不包括末尾的反斜槓
,basename(__FILE__)返回的是文件名
     __FUNCTION__ 返回的是函數名稱
     __METHOD__ 返回的是類的方法名 
     __CLASS__ 返回的是類的名稱
     __NAMESPACE__ 返回的是當前命名空間的名稱數組

4.2 PHP魔術方法的使用

__get/ __set # 訪問不存在的屬性 __call/ __callStatic # 調用不存在的方法 __toString # 對象做爲字符串使用 __invoke # 對象做爲方法使用 

5、三種基本設計模式

5.1 工廠模式

     咱們定義一個專門用來建立其它對象的類。 這樣在須要調用某個類的時候,咱們就不須要去使用new關鍵字實例化這個類,而是經過咱們的工廠類調用某個方法獲得類的實例。
     好處:當咱們對象所對應的類的類名發生變化的時候,只須要在工廠類裏面修改便可,而不用一個一個的去修改緩存

class A { //不容許類直接實例化 或克隆 private function __construct(){} private function __clone(){} } class B { //不容許類直接實例化 或克隆 private function __construct(){} private function __clone(){} } class Factory { public static function getInstance($class) { //類對象的獲取方式經過工廠類 產生 return new $class(); } } //使用 $a = Factory::getInstance('A'); $b = Factory::getInstance('B');

5.2 單例模式

     單例模式的最大好處就是減小資源的浪費,保證整個環境中只存在一個實例化的對象,特別適合資源鏈接類的編寫。
     只實例化一次,內部實例化,對外只有一個開放方法

// 單例模式(口訣:三私一公) class Singleton{ //私有化構造方法,禁止外部實例化對象 private function __construct(){} //私有化__clone,防止對象被克隆 private function __clone(){} //私有化內部實例化的對象 private static $instance = null; // 公有靜態實例方法 public static function getInstance(){ if(self::$instance == null){ //內部實例化對象 self::$instance = new self(); } return self::$instance; } }

5.3 註冊樹模式

     單例模式解決的是如何在整個項目中建立惟一對象實例的問題,工廠模式解決的是如何不經過new創建實例對象的方法。 那麼註冊樹模式想解決什麼問題呢? 在考慮這個問題前,咱們仍是有必要考慮下前兩種模式目前面臨的侷限。 首先,單例模式建立惟一對象的過程自己還有一種判斷,即判斷對象是否存在。存在則返回對象,不存在則建立對象並返回。 每次建立實例對象都要存在這麼一層判斷。 工廠模式更多考慮的是擴展維護的問題。 總的來講,單例模式和工廠模式能夠產生更加合理的對象。怎麼方便調用這些對象呢?並且在項目內創建的對象好像散兵遊勇同樣,不便統籌管理安排啊。於是,註冊樹模式應運而生。無論你是經過單例模式仍是工廠模式仍是兩者結合生成的對象,都通通給我「插到」註冊樹上。我用某個對象的時候,直接從註冊樹上取一下就好。這和咱們使用全局變量同樣的方便實用。 並且註冊樹模式還爲其餘模式提供了一種很是好的想法
     註冊器解決對象屢次調用場景, 減小new新的對象的次數,防止重複建立對象。看對象是不是同一個對象方法,var_dump後看id#1#2等字樣id,它是php內部對象惟一標識

     註冊器模式:Register.php,用來將一些對象註冊到全局的註冊樹上,能夠在任何地方訪問。set():將對象映射到全局樹上,_unset():從樹上移除,get():去註冊到樹上的對象。

class Register { protected static $objects; //註冊 static function set($alias, $object) { self::$objects[$alias] = $object; } //獲取 static function get($key) { if (!isset(self::$objects[$key])) { return false; } return self::$objects[$key]; } //註銷 function _unset($alias) { unset(self::$objects[$alias]); } }

     clipboard.png

5.4 三種基本模式總結

     一、工廠模式的特徵有一個統一輩子成對象的入口,使用工廠方式生成對象,而不是在代碼直接new。爲了後期更好的擴展和修改

     二、單例模式的特徵是對象不可外部實例而且只能實例化一次,當對象已存在就直接返回該對象

     三、註冊樹模式的特徵是對象不用在經過類建立,具備全局對象樹類。解決對象屢次調用場景, 減小new新的對象的次數

6、適配器模式

  1. 能夠將大相徑庭的函數接口封裝成統一的API
  2. 實際應用舉例:PHP的數據庫操做有mysql/mysqli/pdo三種,能夠用適配器模式統一成一致。相似的場景還有cache適配器,能夠將memcache/redis/file/apc等不一樣的緩存函數統一成一致的接口。

     使用適配器策略是爲了更好的兼容。相似於手機電源適配器,若是能用一個充電器對全部手機充電固然是最方便的。不管什麼手機,都只須要拿一個充電器。不然,不一樣手機不一樣充電器,太麻煩。

     新建一個接口 IDatabase 而後在這個接口裏面申明統一的方法體,再讓不一樣的類去實現這個接口,和重寫其抽象方法。當咱們在入口文件使用到不一樣的類的時候,就只是實例化的類名不一樣,其它調用方法體的地方都一致。

/** * 一、新建一個接口 IDatabase 而後在這個接口裏面申明統一的方法體 */ interface IDatabase { //鏈接 function connect($host, $user, $passwd, $dbname); //查詢 function query($sql); //關閉鏈接 function close(); } 
/** * 二、讓不一樣的類去實現這個接口,和重寫其抽象方法。 */ class MySQLi implements IDatabase { protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = mysqli_connect($host, $user, $passwd, $dbname); $this->conn = $conn; } function query($sql) { return mysqli_query($this->conn, $sql); } function close() { mysqli_close($this->conn); } } 

7、策略模式

     策略模式:

  1. 策略模式,將一組特定的行爲和算法封裝成類,以適應某些特定的上下文環境,這種模式就是策略模式
  2. 實際應用舉例,假如一個電商網站系統,針對男性女性用戶要各自跳轉到不一樣的商品類名,而且全部廣告位展現不一樣的廣告,傳統的作法是加入if...else... 判斷。若是新增長一種用戶類型,只須要新增長一種策略便可
  3. 使用策略模式能夠實現Ioc ,依賴倒置,控制反轉,面向對象很重要的一個思想是解耦

     策略模式實現:

  1. 定義一個策略接口文件,定義策略接口,聲明策略
  2. 定義具體類,實現策略接口,重寫策略方法
// 策略的接口文件:約定策略的全部行爲 interface UserStrategy { function showAd(); function showCategory(); } // 實現接口的全部方法 class FemaleUserStrategy implements UserStrategy { function showAd() { echo "2018新款女裝"; } function showCategory() { echo "女裝"; } } // 實現接口的全部方法 class MaleUserStrategy implements UserStrategy { function showAd() { echo "iPhone X"; } function showCategory() { echo "電子產品"; } } 
class Page { /** * @var \IMooc\UserStrategy */ protected $strategy; function index() { echo "AD:"; $this->strategy->showAd(); echo "<br/>"; echo "Category:"; $this->strategy->showCategory(); echo "<br/>"; } function setStrategy(\IMooc\UserStrategy $strategy) { $this->strategy = $strategy; } } $page = new Page; if (isset($_GET['female'])) { $strategy = new \IMooc\FemaleUserStrategy(); } else { $strategy = new \IMooc\MaleUserStrategy(); } $page->setStrategy($strategy); $page->index();

     不須要在page類中判斷業務邏輯,若是在index裏面寫邏輯判斷 if男else女 就會存在‘依賴’,這個是很差的,存在很大耦合,因此把邏輯寫在外部,而且在page裏面增長一個set的方法,這個方法的做用就是‘注入’一個對象。只有再使用時才綁定,這樣之後更方便的替換修改MaleUserStratey類,實現了兩個類的解耦,這就是策略模式的依賴倒置,實現了硬編碼到解耦

     依賴倒置原則:
A. 高層次的模塊不該該依賴於低層次的模塊,他們都應該依賴於抽象
B. 抽象不該該依賴於具體實現,具體實現應該依賴於抽象

     在這裏無論是Page,仍是低層次的MaleUserStratey和FemaleUserStrategy都依賴於抽象userStrategy這個抽象,而UserStrategy不依賴於具體實現,具體實現Female和male都依賴於UserStrategy這個抽象。有點繞,應該是這個關係。

8、數據對象映射模式

    數據對象映射模式,是將對象和數據存儲映射起來,對一個對象的操做會映射爲對數據存儲的操做,比咱們在代碼中new一個對象,那麼使用該模式就能夠將對對象的一些操做,好比說咱們設置的一些屬性,它就會自動保存到數據庫,跟數據庫中表的一條記錄對應起來,數據對象映射模式就是將sql的操做轉化爲對象的操做。

    對象關係映射(英語:Object Relation Mapping,簡稱ORM,或O/RM,或O/R mapping),是一種程序技術,用於實現面向對象編程語言裏不一樣類型系統的數據之間的轉換。從效果上說,它實際上是建立了一個可在編程語言裏使用的--「虛擬對象數據庫」。
    面向對象是從軟件工程基本原則(如耦合、聚合、封裝)的基礎上發展起來的,而關係數據庫則是從數學理論發展而來的,兩套理論存在顯著的區別。爲了解決這個不匹配的現象,對象關係映射技術應運而生。簡單的說:ORM至關於中繼數據

    實例,在代碼中實現數據對象映射模式,咱們將寫一個ORM類,將複雜的SQL語句映射成對象屬性的操做。結合使用數據對象映射模式,工廠模式,註冊模式混合使用

class User { protected $id; protected $data; protected $db; protected $change = false; function __construct($id) { $this->db = Factory::getDatabase(); $res = $this->db->query("select * from user where id = $id limit 1"); $this->data = $res->fetch_assoc(); $this->id = $id; } function __get($key) { if (isset($this->data[$key])) { return $this->data[$key]; } } function __set($key, $value) { $this->data[$key] = $value; $this->change = true; } /** * 析構方法 */ function __destruct() { if ($this->change) { foreach ($this->data as $k => $v) { $fields[] = "$k = '{$v}'"; } $this->db->query("update user set " . implode(', ', $fields) . "where id = {$this->id} limit 1"); } } } 
class Factory { /** 註冊$user * @param $id * @return User */ static function getUser($id) { $key = 'user_'.$id; $user = Register::get($key); if (!$user) { $user = new User($id); Register::set($key, $user); } return $user; } }

9、觀察者模式

1. 觀察者模式( `Observer` ),當一個對象狀態發生改變時,依賴它的對象所有會收到通知,並自動更新 2. 場景:一個事件發生後,要執行一連串更新操做。傳統的編程方式,就是在事件的代碼以後直接加入處理邏輯。當更新的邏輯增多後,代碼會變得難以維護。這種方式是耦合的,入侵式的,增長新的邏輯須要修改事件主體的代碼 3. 觀察者模式實現了低耦合,非入侵式的通知與更新機制 

    觀察者模式實現:

  1. 全部觀察者對象實現統一接口
  2. 被觀察對象持有觀察者句柄,使用Add觀察者()方法
  3. 某一場合,調用觀察方法。Foreach(觀察者句柄數組 as 某一個觀察者)
//事件基類 abstract class EventGenerator { private $observers = array(); function addObserver(Observer $observer) { $this->observers[] = $observer; } function notify() { foreach($this->observers as $observer) { $observer->update(); } } } 
//觀察者接口 interface Observer { function update($event_info = null); }
class Event extends EventGenerator { /** * 觸發事件 */ function trigger() { echo "Event<br/>\n"; $this->notify(); } } class Observer1 implements Observer { function update($event_info = null) { echo "邏輯1<br />\n"; } } class Observer2 implements Observer { function update($event_info = null) { echo "邏輯2<br />\n"; } } $event = new Event; $event->addObserver(new Observer1); $event->addObserver(new Observer2); $event->trigger();

10、原型模式

  1. 原型模式與工程模式做用相似,都是用來建立對象
  2. 與工廠模式的實現不一樣,原型模式是 先建立好一個原型對象,而後經過clone原型對象來建立新的對象。這樣就免去了類建立時的重複初始化操做
  3. 原型模式適用於大對象的建立,建立一個大對象須要很大的開銷,若是每次都new就會消耗很大,原型模式僅需內存拷貝便可

    clipboard.png

11、裝飾器模式

  1. 裝飾器模式(Decorator),能夠動態地添加修改類的功能
  2. 一個類提供了一項功能,若是要在修改並添加額外的功能,傳統的編程模式,須要寫一個子類繼承它,並從新實現類的方法
  3. 使用裝飾器模式,僅需在運行時添加一個裝飾器對象便可實現,能夠實現最大的靈活性
class Canvas { public $data; protected $decorators = array(); //添加裝飾器 function addDecorator(DrawDecorator $decorator) { $this->decorators[] = $decorator; } //執行裝飾器前置操做 先進先出原則 function beforeDraw() { foreach($this->decorators as $decorator) { $decorator->beforeDraw(); } } //執行裝飾器後置操做 先進後出原則 function afterDraw() { //注意,反轉 $decorators = array_reverse($this->decorators); foreach($decorators as $decorator) { $decorator->afterDraw(); } } function draw() { //調用裝飾器前置操做 $this->beforeDraw(); foreach($this->data as $line) { foreach($line as $char) { echo $char; } echo "<br />\n"; } //調用裝飾器後置操做 $this->afterDraw(); }
//裝飾器接口 interface DrawDecorator { function beforeDraw(); function afterDraw(); }
//實現顏色裝飾器實現接口 class ColorDrawDecorator implements DrawDecorator { protected $color; function __construct($color = 'red') { $this->color = $color; } function beforeDraw() { echo "<div style='color: {$this->color};'>"; } function afterDraw() { echo "</div>"; } }

    clipboard.png

12、迭代器模式

  1. 迭代器模式,在不須要了解內部實現的前提下,遍歷一個聚合對象的內部元素
  2. 相比傳統的編程模式,迭代器模式能夠隱藏遍歷元素所需的操做

    應用場景:遍歷數據庫表,拿到全部的user對象,而後用佛 foreach 循環,在循環的過程當中修改某些字段的

class AllUser implements \Iterator { protected $index = 0; protected $data = []; public function __construct() { $link = mysqli_connect('192.168.0.91', 'root', '123', 'xxx'); $rec = mysqli_query($link, 'select id from doc_admin'); $this->data = mysqli_fetch_all($rec, MYSQLI_ASSOC); } //1 重置迭代器 public function rewind() { $this->index = 0; } //2 驗證迭代器是否有數據 public function valid() { return $this->index < count($this->data); } //3 獲取當前內容 public function current() { $id = $this->data[$this->index]; return User::find($id); } //4 移動key到下一個 public function next() { return $this->index++; } //5 迭代器位置key public function key() { return $this->index; } } //實現迭代遍歷用戶表 $users = new AllUser(); //可實時修改 foreach ($users as $user){ $user->add_time = time(); $user->save(); }

十3、代理模式

  1. 在客戶端與實體之間創建一個代理對象(proxy),客戶端對實體進行的操做所有委派給代理對象,隱藏實體的具體實現細節。
  2. Proxy還能夠與業務代碼分離,部署到另外的服務器,業務代碼中經過RPC來委派任務。

    代理模式:數據庫主從,經過代理設置主從讀寫設置

    傳統方式:

    clipboard.png

    須要手動的去選擇主庫和從庫。

    代理模式:

//作約束接口 interface IUserProxy { function getUserName($id); function setUserName($id, $name); }
class Proxy implements IUserProxy { function getUserName($id) { $db = Factory::getDatabase('slave'); $db->query("select name from user where id =$id limit 1"); } function setUserName($id, $name) { $db = Factory::getDatabase('master'); $db->query("update user set name = $name where id =$id limit 1"); } }
$id = 1; $proxy = new \IMooc\Proxy(); $proxy->getUser($id); $proxy->setUser($id, array('name' => 'wang'));

十4、面向對象編程的基本原則

  1. 單一職責:一個類,只需作好一件事情。不要使用一個類來完成很複雜的功能,而是拆分設計成更小更具體的類。
  2. 開放封閉原則:一個類,應該能夠擴展,而不可修改。一個類在實現以後,應該是對擴展開放,對修是改封閉的,不該該使用修改來增長功能,而是經過擴展來增長功能。
  3. 依賴倒置:一個類,不該該強制依賴另外一個類。每一個類對另一個類都是能夠替換的。如:有A、B兩個類,A須要依賴B類,不該該在A類中直接調用B類,而是要使用依賴注入的方式,經過使用注入,將A類依賴的B類的對象注入給A類,B類對於A類來講就是能夠替換的。若是C類實現了和B類同樣的接口,那對於A類,B和C也是能夠隨意替換的。
  4. 配置化: 儘量的使用配置,而不是使用硬編碼。數據參數和常量應該放在配置文件中。像類的關係的定義,也應該是能夠配置的。
  5. 面向接口編程,而不是面向實現編程:只須要關心接口,不須要關心實現。全部的代碼,它只須要關心某一個類實現了哪些接口,而不須要關心這個類的具體實現。

十5、自動加載配置

    若是實現ArrayAcess接口,則能使一個對象屬性的訪問,能夠以數組的方式進行

class Config implements \ArrayAccess { protected $path; protected $configs = array(); //配置文件目錄 function __construct($path) { $this->path = $path; } //獲取數組的key function offsetGet($key) { if (empty($this->configs[$key])) { $file_path = $this->path.'/'.$key.'.php'; $config = require $file_path; $this->configs[$key] = $config; } return $this->configs[$key]; } //設置數組的key function offsetSet($key, $value) { throw new \Exception("cannot write config file."); } function offsetExists($key) { return isset($this->configs[$key]); } function offsetUnset($key) { unset($this->configs[$key]); } }

    controller.php

$config = array( 'home' => array( 'decorator' => array( //'App\Decorator\Login', //'App\Decorator\Template', //'App\Decorator\Json', ), ), 'default' => 'hello world', ); return $config;

    clipboard.png

相關文章
相關標籤/搜索