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