Laravel依赖注入的实现
php简单的依赖注入实现
interface Notify
{
public function send();
}
class SMS implements Notify
{
public function send()
{
echo 'send SMS';
}
}
class Voice implements Notify
{
public function send()
{
echo 'send Voice';
}
}
class User
{
public function sendNotify(Notify $notify)
{
$notify->send();
}
}
$user = new User;
$user->sendNotify(new SMS);
这里我简化了日常的业务逻辑。我们要对一个用户发送通知,可以用短信发送也可以通过语音发送等等。如果不用依赖注入,我们直接在User类你们new一个SMS或者Voice类。当然也是没问题的。但是突然有一天,老板说改成Email对用户发送通知,好了 我们User类要改,具体调用user类的地方也要改。当然实际的业务逻辑更复杂。在日积月累的情况下代码就变得更不好维护了。依赖注入让我们的业务中调用的是接口,而不是具体的类,实现了代码的解耦。这样子,就算老板让我们改成用邮件对用户发送通知,我们只要新建个实现了Notify接口的Email类,然后在具体调用的时候传这个Email类过去就行了。
但是,我们不免看到这样new来new去还是有点多(创建类是必不可少的),Laravel的代码很优雅,我们很少看到它new来new去。那么他是怎么做到的呢?
Laravel中依赖注入的实现
在用Laravel框架的时候,我们在控制器只要把类注入进去,就能直接调用,貌似没有看到具体在哪里new的。那是因为Laraver的控制器要求你都要继承它的BaseController。框架在运行的时候直接从容器中解析你注入进去的类给你实例化了。什么是容器,就是一个智能并高级的工厂。容器不需要被告知如何构建对象,它是通过PHP的反射类ReflectionClass来解析出具体的对象。所以我们就看不到具体在使用Laravel的时候,看它一直在注入类,却看不到它具体是在哪里new的。
容器类(Container)的实现原理
Larvel的容器类在/vendor/laravel/framework/src/Illuminate/Container/Container。我们打开可以看到一个非常长的类。咋一看不知道干嘛的,看不懂,一开始我也是。所以我简化了下容器,自己写了个容器类。
class Container
{
protected $bindings = [];
public function bind($abstract, $concrete = null, $shared = false)
{
if(! $concrete instanceof Closure){
$concrete = $this->getClosure($abstract,$concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
public function getClosure($abstract, $concrete)
{
return function($c, $parameters = []) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
return $c->$method($concrete,$parameters);
};
}
public function make($abstract,$parameters = [])
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete,$parameters);
}
else{
$object = $this->make($concrete,$parameters);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
protected function getConcrete($abstract)
{
if(! isset($this->bindings[$abstract]))
{
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
public function build($concrete,$parameters=[])
{
if($concrete instanceof Closure){
return $concrete($this,$parameters);
}
$reflector = new ReflectionClass($concrete);
if(! $reflector->isInstantiable()) {
return $message= "Target [$concrete] is not instantiable.";
}
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies,$parameters);
return $reflector->newInstanceArgs($instances);
}
protected function getDependencies($parameters,array $primitives = [])
{
$dependencies = [];
foreach ($parameters as $parameter)
{
$dependency = $parameter->getClass();
if(array_key_exists($parameter->name,$primitives)){
$dependencies[] = $primitives[$parameter->name];
}
else if(is_null($dependency)){
$dependencies[] = NULL;
}
else{
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
protected function resolveClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
interface Notify
{
public function send();
}
class SMS implements Notify
{
public function send()
{
echo 'SMS send';
}
}
class Mail implements Notify
{
public function send()
{
echo 'Mail send';
}
}
class Push implements Notify
{
public function send()
{
echo 'Push send';
}
}
class Message
{
protected $notifyTool;
protected $config;
public function __construct(Notify $notify,$parameters = [])
{
$this->notifyTool = $notify;
$this->config = $parameters;
}
public function sendMessage()
{
$this->notifyTool->send();
}
}
$app = new Container();
$app->bind('Notify','Sms');
$app->bind('message','Message');
$msg = $app->make('message',['parameters'=>'额外参数']);
$msg->sendMessage();
容器的实现过程主要依赖php的反射类,一个参数传过来的时候,通过反射可以知道这个类是否可以被实例化,方法存不存在。你看,这样的话我们是不是基本上没有new了。其实不是没new了,而是通过反射,只要写好这样一个通用的容器,new的过程还是有的,只不过都在容器里面了。我写的这个容器还是要通过手动绑定参数来实现,Laravel的服务容器当然更高级,Laravel容器的核心代码基本都在我这里体现了。Laravel的容器能根据类的依赖需求,自动在已经注册、绑定的一堆实例中找到符合需求的,自动注入。
到这里,依赖注入的实现基本上也讲完了。Laravel还有个Facade(门面),其实我感觉这个没多大用,但是有些人在刚接触Laravel的时候有困惑,我稍微带过一下。门面其实把容器里面绑定好的类在套一层,然后用php的魔术方法__callStatic()来实现像是静态的调用不是静态的各个方法。
### 后话
代码想要实现完全解耦是不可能的。我们只能通过巧妙的设计,让耦合度尽可能的降低,方便维护。java的依赖在配置文件用xml配置好,然后就可以实现依赖注入了。由于表达能力有限,自己理解能力也不是完全的彻底。如果看到这篇文章还有疑惑的可以继续参考以下文章:
[1]:http://laravelacademy.org/post/769.html
[2]:https://segmentfault.com/a/1190000002411255