service层的调用方式一般就是实例化引用或者直接使用静态类。后者似乎实现了绝对分离,前者又有过度耦合的嫌疑。
从语义上来说service层之间是有关联的,比如一般的serivce只关注数据库数据的处理,因此这类service应该继承同一个基类。但在调用方式上要低耦合,应该要怎么做,我们先来讨论一下方案
1、使用依赖注入。这在类如laravel框架上很好实施,但其实有进一步解耦的空间,而且如果一个方法依赖很多的服务,就要注入很多的实例,这看起来很丑陋。
2、直接使用静态类。但是这有一个致命缺陷,因为我们已经把service层继承了同一个基类,这时候使用多个service时就会失效。
3、结合以上两种方案我想到的模式就是“享元模式”。这也是我们选择的方案具体操作将在下文给出。
确定了方案后就开始实施。
1、建立基类
<?php
namespace App\Http\Controllers;
class Service{
public static $instance = []; # 实例享元池
public static $reflection = []; # 反射信息
/**
* 构造查询数组
* @param array $need_map
* @param array $init_condition
* @param $condition
* @return array
*/
public static function condition(array $need_map, array $init_condition, &$condition){
if(!is_array($condition)) $condition = [];
// construct
foreach($need_map as $need){
if(isset($init_condition[ $need[0] ])){
if(isset($need[2]) && is_callable($need[2]))
$init_condition[ $need[0] ] = $need[2]($init_condition[ $need[0] ]);
$condition[] = [$need[0], $need[1], $init_condition[ $need[0] ]];
}
}
return $condition;
}
/**
* 支持静态访问
* @param $name
* @param $arguments
* @return mixed
* @throws \Exception
*/
public static function __callStatic($name, $arguments){
// judge pass
self::judgePrivate($name);
// judge reply controller
// self::judgeReplyController();
// action
$key = get_called_class();
if(!isset(self::$instance[$key])) self::$instance[$key] = new static();
return call_user_func([self::$instance[$key], $name], ...$arguments);
}
/**
* 通过反射截止私有方法访问
* @param $name
* @throws \Exception
*/
private static function judgePrivate($name){
// judge pass
try{
$key = get_called_class();
if(!isset(self::$reflection[$key])) self::$reflection[$key] = new \ReflectionClass($key);
$method = self::$reflection[$key]->getMethod($name);
if($method->isPrivate())
throw new \Exception('Method cannot be accessed!');
}catch (\ReflectionException $e){
throw new \Exception($e->getMessage());
}
}
/**
* service 与 controller 匹配判断
* @throws \Exception
*/
private static function judgeReplyController(){
$reply_list = debug_backtrace();
$service = explode('\\', get_called_class());
$service = rtrim(end($service), 'Service');
$class = '';
foreach($reply_list as $reply){
$class_unit = explode('\\', $reply['class']);
$class_unit = end($class_unit);
if(preg_match('/^[A-Za-z]+Controller$/', $class_unit)){
$class = rtrim($class_unit, 'Controller');
break;
}
}
if($service != $class)
throw new \Exception('Service does not match controller!');
}
}
基类实现了享元模式,通过get_called_class()获取具体要调用的service类名,以类名为健名储存一个单例数组也就是享元池。使用__callStatic统一调度实例方法,使用反射获取类信息。
2、service类
我们在建立具体的服务类的时候只使用两种访问级别private和protected。protected是提供控制器使用的方法,private是私有使用。
<?php
namespace App\Http\Controllers\Services;
use App\Http\Controllers\Service;
use Illuminate\Support\Facades\DB;
class TypeOrderService extends Service{
/**
* 统计房间列表所有费用
* @param $id_list
* @return array
*/
protected function countList(array $id_list){
//
}
}
这套方案已在实际项目中实施,可以保证其稳定性,调用也很方便,跟使用静态类的方式是一样的。