search
Homephp教程php手册php重构优化一例模板方法模式应用
php重构优化一例模板方法模式应用Jun 13, 2016 am 10:46 AM
phpoptimizationexistapplicationmethodmodeltemplateexperienceRefactorproject

 最近优化php项目,记录下经验,直接上干活。。。

        php在公司项目中主要用于页面展现,前端有个view,view向后端的service请求数据,数据的传输格式是json。下面看优化前的service的代码:

[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     $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         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的专栏
 

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
php怎么把负数转为正整数php怎么把负数转为正整数Apr 19, 2022 pm 08:59 PM

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

php怎么实现几秒后执行一个函数php怎么实现几秒后执行一个函数Apr 24, 2022 pm 01:12 PM

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php字符串有没有下标php字符串有没有下标Apr 24, 2022 am 11:49 AM

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php怎么除以100保留两位小数php怎么除以100保留两位小数Apr 22, 2022 pm 06:23 PM

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

php怎么根据年月日判断是一年的第几天php怎么根据年月日判断是一年的第几天Apr 22, 2022 pm 05:02 PM

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

php怎么读取字符串后几个字符php怎么读取字符串后几个字符Apr 22, 2022 pm 08:31 PM

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

php怎么替换nbsp空格符php怎么替换nbsp空格符Apr 24, 2022 pm 02:55 PM

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\&nbsp\;||\xc2\xa0)/","其他字符",$str)”语句。

php怎么查找字符串是第几位php怎么查找字符串是第几位Apr 22, 2022 pm 06:48 PM

查找方法:1、用strpos(),语法“strpos("字符串值","查找子串")+1”;2、用stripos(),语法“strpos("字符串值","查找子串")+1”。因为字符串是从0开始计数的,因此两个函数获取的位置需要进行加1处理。

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
2 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
2 weeks agoBy尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
2 weeks agoBy尊渡假赌尊渡假赌尊渡假赌

Hot Tools

EditPlus Chinese cracked version

EditPlus Chinese cracked version

Small size, syntax highlighting, does not support code prompt function

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

Powerful PHP integrated development environment

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

VSCode Windows 64-bit Download

VSCode Windows 64-bit Download

A free and powerful IDE editor launched by Microsoft