深入PHP物件導向、模式與實踐
1 語法
1.1 基礎語法
clone
需要操作原物件,但又不想影響原物件.
$K_back = clone $K;
基本資料型別和陣列都為真複製,即為真副本,當屬性為物件時,為假複製,改變副本仍會影響原物件.解決方案:
//在原物件中新增 function __clone(){ $this->物件 = clone $this->物件 }
__clone
在clone前自動觸發,可以執行一些在備份前的屬性操作.
&
傳遞引用
方法引用傳遞,改變源物件
function set_K(& $K){...} function & get_K(){...}
static
延遲靜態繫結
應用場景:Dog類和Person類都需要一個返回例項化的方法,Dog類和Person類都繼承於Animal抽象類.
abstract class Animal{ public static function create(){ //例項化呼叫類 return new static(); } } class Person extends Animal{...} //返回Person例項化類 Person::create();
攔截器
__get($property)
,訪問未定義的屬性時呼叫.__set($property,$value)
,給未定義的屬性賦值時被呼叫.__isset($property)
,對未定義屬性呼叫isset()方法時呼叫.__unset($property)
,對未定義屬性呼叫unset()方法時呼叫.__call($method,$arg_array)
,呼叫未定義方法時呼叫.__call很有用,但要慎用,因為太靈活.應用場景:有一個專門列印Person類資訊的Person_Writer類,如果通過Person類呼叫Person_Writer類.//Person委託Person_Writer類處理列印事務. class Person { private $writer; ... function __call($method_name,$args){ if(methood_exists($this->wirter,$method_name)){ return $this->writer->$method_name($this); } } //高階__call寫法,當委託方法引數不確定時使用. function __call($method_name,$args){ //當然這裡這樣寫法意義不大,但是call一般都是用call_user_func_array呼叫 $args = $this ; if(methood_exists($this->wirter,$method_name)){ return call_user_func_array( array($this->writer,$method_name),$args); ) } } }
回撥函式
應用場景: 3個類,Product類
,Product_Sale類
,Product_Totalizer類
,要實現:當賣出Product總共價格超過指定金額時,輸出警告.
//Product class Product { public $name; public $price; } //Product_Sale class Product_Sale { private $callbacks; //記錄回撥函式 function register_callback ($callback) { if(! is_callback($callback)){ thow new Exception('callback not callable'); } $this->callbacks[] = $callback; } //執行回撥函式 function sale ($product){ print "{$product->name} : 處理中 n"; foreach($this->callbacks as $callback){ call_user_func($callback , $product); } } } //Produce_Totalizer class Produce_Totalizer { static function warn_amount ($amt) { $count = 0; return function ($produce) use ($amt , &count) { $count += $produce->price; print " count : {count}n" if($count>$amt){ print "quot;超過指定金額{$amt}啦~"; } }; } } //模擬場景 $product_sale = new Produce_Sale(); //指定報警金額為8塊 $product_sale = register_callback(Produce_Totalizer::warn_amount(8)); //賣商品 $product_sale->sale(new Product("Durex",6)); $product_sale->sale(new Produce("Jissbon",5)); //輸出結果 Durex : 處理中 count :6 Jissbon : 處理中 count: 11 超過指定金額8塊啦~
get_class()
和instanceof
get_class(類)
用於判斷是否精準等於類名;
instanceof
可以判斷是否其本身或繼承於某父類.
類中的方法和類中的屬性
get_class_methods('類名')
:獲取類中所有方法.
get_class_vars('類名')
:獲取類中所有public引數;
反射API
2 模式
2.1 組合
問題:課堂類被演講類和研討會類繼承著.但是演講類和研討類都要實現一次性計費和上N次課計費的方法.和輸出計算的方式.
解決方案1: 在課堂類中新增計算一次性付費的方法,上N次課的計費方法和輸出計算方式的方法.
解決方案2: 運用組合,將處理計費和輸出計算方式單獨封裝為一個計費策略類.
abstract class Cost_Strategy { protected $duration; abstract function cost (); abstract function charge_type(); public __construct($duration){ $this->duration = $duration; } } class Timed_Const_Strategy extends Cost_Stratedy { function cost () { //上一次課給5塊錢- -. return $this->duration * 5; } function charge_type(){ return "多次課結算"; } } class Fixed_Const_Strategy extends Cost_Stratedy { function cost (){ return 30 ; } function charge_type(){ return "一次性課結算"; } } abstract class Leason { private $cost_strategy; public __construct(Const_Strategy $cost_strategy){ $this->cost_strategy = $cost_strategy; } function __call($method_name,$args){ $args = $cost_strategy ; if(methood_exists($this->cost_strategy,$method_name)){ return call_user_func_array( array($this->writer,$method_name),$args); ) } } } //運用 $leasons[] = new Seminar(new Timed_Const_Strategy(4)); $leasons[] = new Lecture(new Fixed_Const_Strategy(null)); foreach ($leasons as $leason){ print "leason charge : {$leason->const()}"; print "charge_type : {$leason->charge_type()}" } leason charge 20. charge_type : 多次課結算; leason charge 30. charge_type : 一次課結算;
組合既委託.同級委託.
繼承既父子關係.
3 生成物件
3.1 單例模式
確保系統中只有唯一一個用例.例如系統配置檔案.
重點
1: 構造方法私有.
2: 類本身包含自己的例項化屬性.
class Preferences { private static $instance; private function __construct(){ ... } public static function get_instance(){ if(empty(self::$instance)){ self::$instance = new Preferences(); } return self::$instance; } ... } //使用 $preferences = Preferences::get_instance();
3.2 工廠模式
通過一個父類,生產處多個不同功能的子類.
特點:產品方(新浪微博)和需求方(顯示新浪微博)一一對應.
問題:印象筆記中,來源可能為新浪微博,或者開發者頭條,在印象筆記顯示的時候,兩者的頁首和頁尾是不一樣的.
3.3 抽象模式
RLGL!!!.印象筆記不只要顯示新浪微博內容!!!還要顯示我的新浪賬號,還要該微博啊!!臥槽~憋著急,吻我.
工廠模式主要用於生產一一對應的產品方和需求方,而抽象模式要做的是一個需求方(印象筆記_顯示新浪微博),要多個工廠(把需求方抽象為多個需求方),例如提供新浪內容的工廠,提供新浪賬號的工廠.提供微博內容的評論的工廠等.
程式碼:
abstract class Show_Evernote { abstract function get_header_text(); abstract function get_context(); abstract function get_footer_text(); abstract function get_user(); abstract function get_comment(); } class 顯示新浪微博 extends Show_Evernote{ function get_header_text(){...}; function get_context(){new 新浪微博_內容;} function get_footer_text(){...}; function get_user(){new 新浪微博_賬號 ;} function get_comment(){new 新浪微博_評論;} } //使用 印象筆記控制元件類->內容 = 顯示新浪微博->get_context; 印象筆記控制元件類->賬號 = 顯示新浪微博->get_context; ...
3.4 平行模式
當使用工廠/抽象模式必須要制定具體的建立者(需求方).
平行模式和抽象模式的模型圖一致,但程式碼實現不一樣.
抽象模式中父類均為抽象類,而平行模式中,所以類都為普通類,方便父類的例項化.
在這裡列出顯示印象筆記類的實現程式碼
class Show_Evernote{ private $內容; private $賬號; private $評論; function __construct(內容,賬號,評論){ $this->內容 = 內容; $this->賬號 = 賬號; $this->評論 = 評論; } function get_內容(){ return clone $this->內容); } function get_賬號(){ return clone $this->賬號); } function get_評論(){ return clone $this->評論; } } //使用 $factory = new Show_Evernote( new 新浪微博內容(), new 新浪微博賬號(), new 新浪微博評論() ); 印象筆記控制元件類->顯示印象筆記 = $factory;
其實大家可以發現,原型模式只不過只在最頂層類中包裝了一下各元件子類而已,然而這樣可以輕鬆的組合他們,例如實現一個顯示新浪微博內容,但要顯示開發者頭條賬號的需求?
4 使用物件
4.1 組合模式
組合模式,可以理解為單一物件管理組合物件(聚合元件),最終組合體下的各個組合部件最好型別一致.不然特殊性越多,需要判斷就越多.
假設捶背男,洗腳男,洗髮男,用來服務一個人(妹子).
假設妹子的幾個部位可用的服務男均為無限個.
//建立一個妹子 $妹子 = new 人(); //新增洗腳男、捶背男 $妹子->add_man(new 洗腳男); $妹子->add_man(new 捶背男); //迴圈所有男的給予舒服的方法. $妹子->計算舒服程度();
這是一個很理想的組合模式,在現實情況,我們使用組合模式,可能不得不建立多種型別的洗腳男,需要新增許多判斷條件.
4.2 裝飾模式
裝飾模式,首先洗腳男,洗髮男,捶背男都是人,但是如果,一個男的又捶背,又洗髮,這怎麼玩?.add_man
兩次?這不科學吧,來給這些男的裝飾一下吧~
abstract class 人{ ... abstract function get_well(); } class 男 extends 人 { //無論你是神馬男,服務你,你就能獲得10點舒服度. private $well = 10; function get_well(){ return $this->well(); } } abstract class 裝飾男型別 extends 人 { protected $人; function __construct(人 $人){ $this->人 = $人; } } class 捶背裝飾 extends 型別男裝飾{ function get_well(){ return $this->人->get_well()+30; } } class 洗髮裝飾 extends 型別男裝飾{ function get_well(){ return $this->人->get_well()+20; } } class 洗褪裝飾 extends 型別男裝飾{ //老子不喜歡別人碰我的毛褲. function get_well(){ return $this->人->get_well()-20; } } //建立捶背,能給予的舒服指數 - -嘻嘻. $人 = new 捶背裝飾(new 男); $人->get_well(); // 10+30 = 40 //來來來,全能選手,捶背、洗髮、洗腿一起來 $人 = new 洗腳裝飾(new 洗髮裝飾(new 捶背裝飾(new 男()))); //10+30+20-20 = 40,注意順序,由裡到外執行.
裝飾模式,既(組合+繼承),基類方法一定要儘量少,不然子類可能有它不該有的方法.直接類繼承,她只可能是一種形態,而她的多種形態可能一併擁有的時候,應該運用組合.
繼承即單一多型,組合既多種多型.
這個例子中,你可以新增女,然後把裝飾男型別改為裝飾通用型別,但每個get_well()都要多一個判斷是男還是女(如果給予的舒服程度不一樣).
這只是確保不可能出現在男
,女
之外的第三種人,如果基類為動物,給予服務的可能是雞,鵝,鴨,那麼裝飾型別應該運用工廠模式,動物形態和裝飾形態一一對應.方便擴充.
除了服務型別,服務男的樣子也很重要,這就多了一種裝飾,現在有裝飾男型別
和相貌男型別
,這種情況怎麼破,其實類似.
//如何獲取捶背的帥哥麥?, $人 =new 男型別(new 捶背(new 帥哥麥(new 男())));
4.3 外觀模式
即給外部系統提供清晰介面
例如當Model層寫得很混亂,但是裡面的方法還能用,那我們的Controller層應該列舉一些清晰的訪問方法來供View層訪問.外觀模式,強調的是清晰的訪問介面.
5 執行任務
5.1 策略模式
給類新增功能.物件要顯式的呼叫它.
繼續剛才的洗腳男和人的故事吧…你丫的爽完了要給錢吧?支付寶?微信?現金?
這個付款方式有多種,實現方法不應該放在人
類中,而是應該委託給別的類
abstract class 人 { protectd $支付方式; function set_支付方式(){...} function 付款(金額){ return $支付方式->付款($金額); } } abstract class 付款{ abstract function 付款($金額); } class 支付寶付款 extends 付款{ function 付款($金額){ return 外接支付寶付款流程($金額); } } ... //使用 $男 =new 男(); ///爽爽爽 ... //結賬 $支付寶支付賬單 = new 支付寶付款($金額); $人 = new 男(); $人->set_支付方式(new 支付寶付款()); $人->付款();
5.2 觀察者模式
當被觀察者發生變化,觀察者需要被通知.
當資料發生變化,頁面需要被通知.
使用步驟:
- 觀察者載入到被觀察者中.
- 被觀察者通知觀察者.
例如登陸類(被觀察)狀態改變,要出發郵件系統和日誌系統(觀察者)
interface 被觀察者{ function attach(觀察者); function detatch(觀察者); function notify(); } class Login implements 被觀察者{ private $觀察者; function __construct(){ $this->觀察者 = array(); } function attach($觀察者){ $this->觀察者 = $觀察者; } function detach($觀察者){ //刪除某個觀察者的操作; } function notify(){ foreach ($this->觀察者 as $單個觀察者){ $單個觀察者->update($this); } } } interface 觀察者{ function update(被觀察者); } abstract class Login_觀察者 implements 觀察者{ private $login; function __construct (Login $login){ $this->login = $login; $login->attach($this); } function update(觀察者 $觀察者){ if ($觀察者 ===$this->login){ $this->do_update($觀察者); } } abstract function do_update(Login $login); } class 郵件觀察者 extends 登陸觀察者 { function do_update(Login $login){ //判斷條件 傳送郵件 } } class 日誌觀察者 extends 登陸觀察者 { function do_update(Login $login){ //判斷條件 記錄到日誌; } } //使用 $login = new Login(); new 郵件觀察者 ($login); new 日誌觀察者 ($login);
PHP有內建的SPL實現上述的觀察者模式.
5.3 訪問者模式
問題: 在一個軍隊中,有很多軍隊,軍隊下面可能包含軍隊/步兵/弓箭手,這時我們要顯示一個軍隊的戰鬥力/需要糧食的各級分配?(遍歷物件並設定顯示方法).怎麼辦?.解決辦法是軍隊還是儲存自己的基本資訊,設定一個訪問者,訪問者包含總戰鬥力方法和總糧食的方法.
訪問者
abstract class 軍隊訪問者{ abstract function 訪問(單元); function 訪問軍隊($軍隊){ $this->訪問($軍隊); } function 訪問弓箭手($弓箭手){ $this->訪問($弓箭手); } //這裡重複定義了大量程式碼,其實可以用call來替代 function __call($method_name,$args){ if(strrpos($method_name, "訪問")){ return call_user_func_array( array($this,"訪問"),$args ); } } } class 軍隊戰鬥力訪問者 extends 軍隊訪問者{ private $text=""; function 訪問($單元){ $ret = ""; $pad = 4*$單元->getDpth(); //設定顯示深一級前面多4個空格. $ret .= sprintf( "%{$pad}s",""); $ret .= get_class($單元). ": "; $ret .= "戰鬥力: " .$單元->bombardStrenth()."n"; $this->text .=$ret; } function get_text(){ return $this->text; } }
被訪問者
abstract class 單元{ function 接受($軍隊訪問者){ $method = "訪問_".get_class($this); $軍隊訪問者->$method($this); } private $depth; protected function set_depath($depth){ $this->depth=$depth; } function get_depth(){ return $this->depth; } ... } abstract class 綜合單元 extends 單元{ function 接受($軍隊訪問者){ parent::接受($軍隊訪問者) foreach($this->單元集合 as $this_unit){ $this->unit->接受($軍隊訪問者); } } } class 軍隊 extends 綜合單元{ function bombardStrenth(){ $ret =0; foreach($this-units() as $unit){ $ret += $unit->bombardStrenth(); } return $ret } } class 弓箭手 extends 單元{ function bombardStrenth(){ return 4; } }
呼叫
$main_army = new Army(); $main_army->add_unit(new 步兵()); $main_army->add_unit(new 弓箭手()); $軍隊戰鬥力訪問者_例項 =new 軍隊戰鬥力訪問者(); $main_army->接受(均分戰鬥力訪問者); print $軍隊戰鬥力訪問者->get_text();
輸出
軍隊: 戰鬥力: 50 步兵: 攻擊力 :48 弓箭手: 攻擊力: 4
5.4 命令模式
例子為Web頁面的login和feed_back,假如都需要使用ajax提交,那麼問題來了,將表單封裝好提交上去,得到了返回結果.如何根據返回結果跳轉不同的頁面?.
有些同學就說了,login和feed_back各自寫一個方法憋,提交的時候呼叫各自的方法.
然後再來個logout命令..增加..刪除..命令怎麼辦..
命令模式比較適合命令執行
例如登陸,反饋等簡單隻需要判斷是否成功的任務
命令:
abstract class Command{ abstract function execute(Conmmand_Context $context); } class Login_Command extends Command{ function execute(CommandContext $context){ $managr =Register::getAccessManager(); $user = $context->get("username"); $pass = $context->get('pass'); $user_obj = $manager->login($user,$pass); if(is_null($user_obj)){ $context->setError($manager->getError()); return false; } $context->addParam("user",$user_obj); return true; } }
部署命令的呼叫者
class Command_Facotry{ public function get_command($action){ $class = UCFirst(strtolower($action))."_Command"; $cmd = new $class(); return $cmd; } }
客戶端
class Controller{ private $context; function __construct(){ //Command_Context主要用來儲存request和params $this->context =new Command_Context(); } function process(){ $cmd Command_Factory::get_commad($this->context->get('action')); if(!$cmd-execute($this->context)){ //錯誤處理 }else{ //成功 分發檢視 } } }
使用
$controller =new Controller(); $context = $controller->get_context(); $context->add_param('action','login'); $context->add_param('username','404_k'); $context->add_param('pass','123456'); $controller->process();
相關文章
- php中的程式導向與物件導向PHP物件
- 【物件導向的PHP】之模式:目錄物件PHP模式
- PHP物件導向PHP物件
- PHP 物件導向 (九)物件導向三大特徵PHP物件特徵
- PHP物件導向(三)PHP物件
- 1.設計模式與物件導向設計模式物件
- 物件導向與程式導向物件
- 程式導向與物件導向物件
- PHP 物件導向 (十)TraitsPHP物件AI
- PHP物件導向之&引用PHP物件
- PHP 物件導向 - 物件的淺拷貝與深拷貝PHP物件
- Golang 物件導向深入理解Golang物件
- 深入理解PHP物件導向之後期靜態繫結PHP物件
- PHP學習4——物件導向PHP物件
- PHP 核心技術 --物件導向PHP物件
- PHP 物件導向基礎概念PHP物件
- PHP 物件導向 (十一)反射類PHP物件反射
- TypeScript與物件導向TypeScript物件
- 《JavaScript物件導向精要》之六:物件模式JavaScript物件模式
- Java物件導向——類與物件Java物件
- JS物件導向設計模式JS物件設計模式
- 物件導向-物件導向思想物件
- PHP物件導向(OOP)—-分頁類PHP物件OOP
- PHP MySQL (三)物件導向 事務PHPMySql物件
- PHP 物件導向 (二)類屬性PHP物件
- PHP 物件導向 (五)靜態方法PHP物件
- PHP 物件導向 (六)魔術方法PHP物件
- PHP基礎之物件導向篇PHP物件
- php基礎語法_物件導向PHP物件
- Javascript 設計模式之物件導向與 UML 類圖JavaScript設計模式物件
- python物件導向思想(類與物件)Python物件
- 物件導向與UML圖物件
- js-物件導向-設計模式-命令模式JS物件設計模式
- 物件導向-設計模式-建立型物件設計模式
- 淺談PHP物件導向程式設計PHP物件程式設計
- PHP 物件導向 (三)名稱空間PHP物件
- PHP基礎之物件導向講解PHP物件
- PHP中物件導向的分頁類PHP物件
- JavaScript物件導向—深入ES6的classJavaScript物件