php這樣寫是不是會優雅一些


使用引用

場景一:遍歷一個數組獲取新的數據結構

也許你會這樣寫:

// 申明一個新的數組,組裝成你想要的數據 $tmp = []; foreach ($arr as $k => $v) { // 取出你想要的數據 $tmp[$k]['youwant'] = $v['youwant']; ... // 一系列判斷得到你想要的數據 if (...) { $tmp[$k]['youwantbyjudge'] = 'TIGERB'; } ... } // 最后得要你想要的數組$tmp ------------------------------------------------------- // 也許你覺着上面的寫法不是很好,那我們下面換種寫法 foreach ($arr as $k => $v) { // 一系列判斷得到你想要的數據 if (...) { // 復寫值為你想要的 $arr[$k]['youwantbyjudge'] = 'TIGERB' } ... // 干掉你不想要的結構 unset($arr[$k]['youwantdel']); } // 最后我們得到我們的目標數組$arr 

接下來我們使用引用值:

foreach ($arr as &$v) { // 一系列判斷得到你想要的數據 if (...) { // 復寫值為你想要的 $v['youwantbyjudge'] = 'TIGERB' } ... // 干掉你不想要的結構 unset($v['youwantdel']); } unset($v); // 最后我們得到我們的目標數組$arr 

使用引用是不是使我們的代碼更加的簡潔,除此之外相對於第一種寫法,我們節省了內存空間,尤其是再操作一個大數組時效果是及其明顯的。

場景二:傳遞一個值到一個函數中獲取新的值

基本和數組遍歷一致,我們只需要聲明這個函數的這個參數為引用即可,如下:

function decorate(&$arr = []) { # code... } $arr = [ .... ]; // 調用函數 decorate($arr); // 如上即得到新的值$arr,好處還是節省內存空間 使用try…catch… 加入有下面一段邏輯: class UserModel { public function login($username = '', $password = '') { code... if (...) { // 用戶不存在 return -1; } code... if (...) { // 密碼錯誤 return -2; } code... } } class UserController { public function login($username = '', $password = '') { $model = new UserModel(); $res = $model->login($username, $password); if ($res === -1) { return [ 'code' => '404', 'message' => '用戶不存在' ]; } if ($res === -2) { return [ 'code' => '400', 'message' => '密碼錯誤' ]; } code... } } 

我們用try…catch…改寫后:

class UserModel { public function login($username = '', $password = '') { code... if (...) { // 用戶不存在 throw new Exception('用戶不存在', '404'); } code... if (...) { // 密碼錯誤 throw new Exception('密碼錯誤', '400'); } code... } } class UserController { public function login($username = '', $password = '') { try { $model = new UserModel(); $res = $model->login($username, $password); // 如果需要的話,我們可以在這里統一commit數據庫事務 // $db->commit(); } catch (Exception $e) { // 如果需要的話,我們可以在這里統一rollback數據庫事務 // $db->rollback(); return [ 'code' => $e->getCode(), 'message' => $e->getMessage() ] } } } 

通過使用try…catch…使我們的代碼邏輯更加清晰,try…里只需要關注業務正常的情況,異常的處理統一在catch中。所以,我們在寫上游代碼時異常直接拋出即可。

使用匿名函數

構建函數或方法內部的代碼塊

假如我們有一段邏輯,在一個函數或者方法里我們需要格式化數據,但是這個格式化數據的代碼片段出現了多次,如果我們直接寫可能會想下面這樣:

function doSomething(...) { ... // 格式化代碼段 ... ... // 格式化代碼段[重復的代碼] ... } 

我相信大多數的人應該不會像上面這么寫,可能都會像下面這樣:

function doSomething(...) { ... format(...); ... format(...); ... } // 再聲明一個格式花代碼的函數或方法 function format() { // 格式化代碼段 ... } 

上面這樣的寫法沒有任何的問題,最小單元化我們的代碼片段,但是如果這個format函數或者方法只是doSomething使用呢?我通常會向下面這么寫,為什么?因為我認為在這種上下文的環境中format和doSomething的一個子集。

function doSomething() { ... $package = function (...) use (...) { // 同樣use后面的參數也可以傳引用 // 格式化代碼段 ... }; ... package(...); ... package(...); ... } 

實現類的【懶加載】和實現設計模式的【最少知道原則】

假如有下面這段代碼:

class One { private $instance; // 類One內部依賴了類Two // 不符合設計模式的最少知道原則 public function __construct() { $this->intance = new Two(); } public function doSomething() { if (...) { // 如果某種情況調用類Two的實例方法 $this->instance->do(...); } ... } } ... $instance = new One(); $instance->doSomething(); ... 

上面的寫法有什么問題?

不符合設計模式的最少知道原則,類One內部直接依賴了類Two

類Two的實例不是所有的上下文都會用到,所以浪費了資源,有人說搞個單例,但是解決不了實例化了不用的尷尬

所以我們使用匿名函數解決上面的問題,下面我們這么改寫:

class One { private $closure; public function __construct(Closure $closure) { $this->closure = $closure; } public function doSomething() { if (...) { // 用的時候再實例化 // 實現懶加載 $instance = $this->closure(); $instance->do(...) } ... } } ... $instance = new One(function () { // 類One內部外部依賴了類Two return new Two(); }); $instance->doSomething(); ... 

減少對if…else…的使用

如果你碰見下面這種類型的代碼,那一定是個黑洞。

function doSomething() { if (...) { if (...) { ... } esle { ... } } else { if (...) { ... } esle { ... } } } 

提前return異常

細心的你可能會發現上面這種情況,可能絕大多數else代碼里都是在處理異常情況,更有可能這個異常代碼特別簡單,通常我會這么去做:

// 如果是在一個函數里面我會先處理異常的情況,然后提前return代碼,最后再執行正常的邏輯 function doSomething() { if (...) { // 異常情況 return ...; } if (...) { // 異常情況 return ...; } // 正常邏輯 ... } // 同樣,如果是在一個類里面我會先處理異常的情況,然后先拋出異常 class One { public function doSomething() { if (...) { // 異常情況 throw new Exception(...); } if (...) { // 異常情況 throw new Exception(...); } // 正常邏輯 ... } } 

關聯數組做map

如果我們在客戶端做決策,通常我們會判斷不同的上下文在選擇不同策略,通常會像下面一樣使用if或者switch判斷:

class One { public function doSomething() { if (...) { $instance = new A(); } elseif (...) { $instance = new A(); } else { $instance = new C(); } $instance->doSomething(...); ... } } 

上面的寫法通常會出現大量的if語句或者switch語句,通常我會使用一個map來映射不同的策略,像下面這樣:

class One { private $map = [ 'a' => 'namespace\A', // 帶上命名空間,因為變量是動態的 'b' => 'namespace\B', 'c' => 'namespace\C' ]; public function doSomething() { ... $instance = new $this->map[$strategy];// $strategy是'a'或'b'或'c' $instance->doSomething(...); ... } } 

使用接口

為什么要使用接口?極大的便於后期的擴展和代碼的可讀性,例如設計一個優惠系統,不同的商品只是在不同的優惠策略下具備不同的優惠行為,我們定義一個優惠行為的接口,最后對這個接口編程即可,偽代碼如下

Interface Promotion { public function promote(...); } class OnePromotion implement Promotion { public function doSomething(...) { ... } } class TwoPromotion implement Promotion { public function doSomething(...) { ... } } 

控制器拒絕直接的DB操作

最后我想說的是永遠拒絕在你的Controller里直接操作DB,為什么?我們的程序絕大多數的操作基本都是增刪改查,可能是查詢的where條件和字段不同,所以有時候我們可以抽象的把對數據庫增刪改查的方法寫到model中,通過參數暴露我們的where,fields條件。通常這樣可以很大程度的提高效率和代碼復用。比如像下面這樣:

class DemoModel implement Model { public function getMultiDate($where = [], $fields = ['id'], $orderby = 'id asc') { $this->where($where) ->field($fields) ->orderby($orderby) ->get(); } }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM