程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> php重構優化一例——模板方法模式應用

php重構優化一例——模板方法模式應用

編輯:關於PHP編程

 最近優化php項目,記錄下經驗,直接上干活。。。

        php在公司項目中主要用於頁面展現,前端有個view,view向後端的service請求數據,數據的傳輸格式是json。下面看優化前的service的代碼:

[php]
<?php 
require_once('../../../global.php'); 
require_once(INCLUDE_PATH . '/discache/CacherManager.php'); 
require_once(INCLUDE_PATH.'/oracle_oci.php'); 
require_once(INCLUDE_PATH.'/caihui/cwsd.php'); 
header('Content-type: text/plain; charset=utf-8'); 
$max_age = isset($_GET['max-age']) ? $_GET['max-age']*1 : 15*60; 
if($max_age < 30) { 
    $max_age = 30; 

header('Cache-Control: max-age='.$max_age); 
// 通過將url進行hash作為緩沖key 
$url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 
$url_hash = md5($url); 
//echo "/finance/hs/marketdata/segment/${url_hash}.json"; 
if (!CacherManager::cachePageStart(CACHER_MONGO, "/finance/hs/marketdata/segment/${url_hash}.json", 60*60)) { 
 
// 查詢條件 
$page = isset($_GET['page']) ? $_GET['page']*1 : 0; 
$count = isset($_GET['count']) ? $_GET['count']*1 : 30; 
$type = isset($_GET['type']) ? $_GET['type'] : 'query'; 
$sort = isset($_GET['sort']) ? $_GET['sort'] : 'symbol'; 
$order = isset($_GET['order']) ? $_GET['order'] : 'desc'; 
$callback = isset($_GET['callback']) ? $_GET['callback'] : null; 
$fieldsstring = isset($_GET['fields']) ? $_GET['fields'] : null; 
$querystring = isset($_GET['query']) ? $_GET['query'] : null; 
$symbol=isset($_GET['symbol'])?$_GET['symbol']:''; 
$date=isset($_GET['date'])?$_GET['date']:''; 
 
if ($type == 'query') { 
    $queryObj = preg_split('/:|;/', $querystring, -1); 
    for($i=0; $i<count($queryObj); $i=$i+2){ 
        if(emptyempty($queryObj[$i])) continue; 
        if($queryObj[$i]=='symbol'){ 
            $symbol = $queryObj[$i+1]; 
        } 
        if($queryObj[$i]=='date'){ 
            $date = $queryObj[$i+1]; 
        } 
    } 
}  
 
// 查詢列表 
$oci = ntes_get_caihui_oci(); 
$stocklist = array(); 
$cwsd = new namespace\dao\caihui\Cwsd($oci); 
                       
$stockcurror = $cwsd->getCznlList($symbol,$date,$sort,$order,$count*($page),$count); 
$sumrecords=$cwsd->getRecordCount($symbol,$date); 
$i=0; 
//var_dump($symbol,$date,$sort,$order,$count*($page),$count); 
foreach($stockcurror as $item){ 
    $item['RSMFRATIO1422']=isset($item['RSMFRATIO1422'])?number_format($item['RSMFRATIO1422'],2).'%':'--'; 
    $item['RSMFRATIO1822']=isset($item['RSMFRATIO1822'])?number_format($item['RSMFRATIO1822'],2).'%':'--'; 
    $item['RSMFRATIO22']=isset($item['RSMFRATIO22'])?number_format($item['RSMFRATIO22'],2).'%':'--'; 
     
    $item['RSMFRATIO10']=isset($item['RSMFRATIO10'])?number_format($item['RSMFRATIO10'],2):'--'; 
    $item['RSMFRATIO12']=isset($item['RSMFRATIO12'])?number_format($item['RSMFRATIO12'],2):'--'; 
    $item['RSMFRATIO4']=isset($item['RSMFRATIO4'])?number_format($item['RSMFRATIO4'],2):'--'; 
    $item['RSMFRATIO18']=isset($item['RSMFRATIO18'])?number_format($item['RSMFRATIO18'],2):'--'; 
    $item['RSMFRATIO14']=isset($item['RSMFRATIO14'])?number_format($item['RSMFRATIO14'],2):'--'; 
 
    $item['CODE']=$item['EXCHANGE'].$item['SYMBOL']; 
    //$item['REPORTDATE']=isset($item['REPORTDATE'])?$item['REPORTDATE']:'--'; 
    $stocklist[$i] = $item; 
    $i=$i+1; 

 
 
// 輸出結果 
$result = array(); 
// 頁碼page、每頁數量count、結果總數total、分頁數pagecount、結果列表list 
$result['page'] = $page; 
$result['count'] = $count; 
$result['order'] = $order; 
$result['total'] = $i;//$stockcurror->count(); 
$result['pagecount'] = ceil($sumrecords['SUMRECORD']/$count); 
$result['time'] = date('Y-m-d H:i:s'); 
$result['list'] = $stocklist; 
if(emptyempty($callback)){ 
    echo json_encode($result); 
}else{ 
    echo $callback.'('.json_encode($result).');'; 

 
CacherManager::cachePageEnd(); 

?> 
        下面看一下這個service具體完成的功能:

        1. 6-16行,准備緩存參數,開啟緩存。
        2. 19-41行,提取請求參數。
        3. 44-49行,連接、查詢數據庫。
        4. 50-67行,將數據庫查詢結果放入數組。
        5. 71-84行,准備json數據。
        6. 86-87行,關閉緩存。

        如果只看這一個文件,存在的問題有:
        1. 19-86行,沒有縮進。
        2. 44行,每次請求都會重新連接數據庫。
        3. 53-61行,重復的邏輯,可以提取為一個函數,然後通過迭代完成。
        如果大部分後端Service都采用這個結構,那麼問題就是所有的Service都需要經過:開啟緩存,取參,獲取數據,json轉化,關閉緩存這一系列的過程。而在所有過程中,除了獲取數據的邏輯,其他的過程都是一樣的。在代碼中存在著大量的重復邏輯,甚至給人一種“復制-粘貼”的感覺,這嚴重的違背了DRY原則(Don't Repeat Yourself)。所以,這裡需要運用面向對象的思想對其重構。而在我重構的過程中,腦海中始終謹記著一個原則——封裝變化原則。所謂封裝變化,就是區分系統中不變的和可變的,將可變的進行封裝,這樣可以很好過應對變化。
        通過上面的分析,只有獲取數據的邏輯是變化的,其他的邏輯是不變的。所以需要對獲取數據的邏輯進行封裝,具體的封裝方式可以采用繼承或組合。我采用的是繼承的方式,首先將service的處理過程抽象為:
        service(){
                startCache();
                getParam();
                getData(); // 抽象方法,由子類實現
                toJson();
                closeCache();
        }
         抽象出ServiceBase類,由子類繼承,實現相應的獲取數據的邏輯,子類不需要處理其他的取參、緩存等邏輯,這些都被ServiceBase類處理了。
[php]
abstract class ServiceBase {         
    public function __construct($cache_path, $cache_type, $max_age, $age_explore) { 
        // 獲取請求參數 
        $this->page = $this->getQueryParamDefault('page', 0, INT); 
        // 省略其他的獲取參數的邏輯 
        ……       
 
        // 生成響應 
        $this->response(); 
    } 
 
    /**
     * 
     * 子類實現,返回數組格式的數據
     */ 
    abstract protected function data(); 
 
    /**
     * 
     * 子類實現,返回所有數據的總數
     */ 
    abstract protected function total(); 
 
    private function cache() { 
        $url = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; 
        $url_hash = md5($url); 
        $key = $this->cache_path.$url_hash.'.json'; 
        if(!CacherManager::cachePageStart($this->cache_type, $key, $this->age_cache)){ 
            $this->no_cache(); 
            CacherManager::cachePageEnd(); 
        } 
    } 
 
    private function no_cache(){ 
        $data = $this->data(); 
        $total = $this->total(); 
        $this->send_data($data, $total); 
    } 
 
    private function send_data($data, $total){ 
        // 進行json轉化,省略具體代碼 
    } 
 
    private function response() { 
        header('Content-type: text/plain; charset=utf-8'); 
        header('Cache-Control: max-age='.$this->age_explore);     
        if($this->cache_type == NONE || self::$enable_cache == false){ 
            $this->no_cache(); 
        }else{ 
            $this->cache(); 
        } 
    } 

        這就是各個service的抽象父類,有兩個抽象方法data和total,data是返回數組格式的數據,tatol是由於分頁加入的。具體的service只要繼承ServiceBase並實現data和total方法即可,其他的邏輯都是復用的父類的。實際上,優化後的ServiceBase是使用了模板方法模式(Template Method),父類定義算法處理的流程(service的處理過程),子類實現某個具體變化的步驟(具體service的獲取數據的邏輯)。通過使用模板方法模式可以保證步驟的變化對於客戶端是透明的,並且可以復用父類中的邏輯。

        下面是上述php采用ServiceBase後的代碼:

[php]
class CWSDService extends ServiceBase{ 
    function __construct(){ 
        parent::__construct(); 
        $oci = ntes_get_caihui_oci(); 
        $this->$cwsd = new namespace\dao\caihui\Cwsd($oci); 
    } 
    public function data(){ 
        $stocklist = array(); 
        $stockcurror = $this->cwsd->getCznlList($this->query_obj['symbol'],  
            $this->query_obj['symbol'], $sort, $order, $count*($page), $count); 
        $filter_list = array('RSMFRATIO1422', 'RSMFRATIO1822', 'RSMFRATIO22', 
            'RSMFRATIO10', 'RSMFRATIO12', 'RSMFRATIO4', 'RSMFRATIO18', 
            'RSMFRATIO14'); 
        $i=0; 
        foreach($stockcurror as $item){ 
            foreach($filter_list as $k) 
                $this->filter($item, $k); 
            $item['CODE']=$item['EXCHANGE'].$item['SYMBOL']; 
            $stocklist[$i] = $item; 
            $i=$i+1; 
        } 
        return $stocklist; 
    } 
    public function total(){ 
        return $sumrecords=$this->cwsd->getRecordCount($this->query_obj['symbol'],  
            $this->query_obj['symbol']); 
    } 
    private function filter($item, $k){ 
        isset($item[$k])?number_format($item[$k],2).'%':'--'; 
    } 

new CWSDService('/finance/hs/realtimedata/market/ab', MONGO, 30, 30); 
 代碼量從87減少到32行,是因為大部分的邏輯都由父類完成,具體service只需要關注自己的業務邏輯就可以了。通過上面代碼可以看出繼承可以實現代碼復用,多個子類中的相同的邏輯可以提取到父類中達到復用的目的;同時,繼承也增加了父類和子類之間的耦合性,這也就是組合由於繼承的方面,如果這個例子采用組合來封裝變化,則具體的實現就是策略模式,將具體獲取數據的邏輯看成是策略,不同的service就是不同的策略,由於時間原因,不再贅述。。。

摘自 chosen0ne的專欄
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved