php重构优化一例——模板方法模式应用

        最近优化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 < 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(empty($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(empty($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类处理了。

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后的代码:

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就是不同的策略,由于时间原因,不再赘述。。。





  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值