laravel 服务容器实例——深入理解IoC模式
刚刚接触laravel,对于laravel的服务容器不是很理解。看了《Laravel框架关键技术解析》和网上的一些资料后对于服务容器有了一些自己的理解,在这里分享给大家
1、依赖
IoC模式主要是用来解决系统组件之间相互依赖关系的一种模式。那么什么是依赖呢?下面给出依赖的实例
<?php
//设计公共接口
interface Go_To_School
{
public function go();
}
//实现交通工具类
class Foot implements Go_To_School
{
public function go()
{
echo 'walt to school';
}
}
//设计学生类,实现去学校时要依赖的交通工具实例
class Student
{
private $trafficTool;
public function __construct()
{
//产生依赖
$this->trafficTool = new Foot();
}
public function go_to_school()
{
$this->trafficTool->go();
}
}
$student = new Student();
$student->go_to_school();
这里要实现的功能是学生去学校,当创建了一个学生实例的时候,同时也创建了一个交通工具的实例。可以看到学生和交通工具之间不可避免的产生了一个依赖。在程序中依赖可以理解为一个对象实现某个功能时需要其他对象相关功能的支持。
当然交通工具还有很多,接着完善代码
<?php
//设计公共接口
interface Go_To_School
{
public function go();
}
//实现不同交通工具类
class Foot implements Go_To_School
{
public function go()
{
echo 'walt to school';
}
}
class Car implements Go_To_School
{
public function go()
{
echo 'drive to school';
}
}
class Bicycle implements Go_To_School
{
public function go()
{
echo 'ride to school;';
}
}
/*
* 少量的依赖并不会有太过直观的影响,我们随着这个例子逐渐铺开,让大家慢慢意识到,当依赖达到一个量级时,是怎样一番噩梦般的体验
*/
class more implements Go_To_School
{
public function go()
{
//...................
}
}
/*
*
*
*
*
* more and more
*
*
*
*
*
*/
//设计学生类,实现去学校时要依赖的交通工具实例
class Student
{
private $trafficTool;
public function __construct()
{
//产生依赖
$this->trafficTool = new Foot();
//$this->trafficTool = new Car();
//$this->trafficTool = new Bicycle();
/*
*
*
*
*
* more and more
*
*
*
*
*/
}
public function go_to_school()
{
$this->trafficTool->go();
}
}
$student = new Student();
$student->go_to_school();
是不是很恐怖!当用new关键字在一个组件内部实例化一个对象时就解决了一个依赖,但同时也引入了另一个严重的问题——耦合。在简单情况下可能看不出耦合有多大问题,但是如果需求改变,如需要实例化的交通工具是自行车、汽车甚至是在设计中还不太清楚的工具时,就需要改变实例化的对象,如果又有很多地方用到了这段代码,而这个程序又不是你写的,这时你面对的将是噩梦。所以,这里我们不应该在学生内部固化’交通工具‘的初始化行为,而转由外部负责
2、简单工厂模式
原理:由一个工厂类根据传入的参数(一般是字符串参数),动态决定应该创建哪一个产品子类(这 些产品子类继承自同一个父类或接口)的实例,并以父类形式返回。
适用情况:所有的产品子类都有同一个父类(或接口),属于同一个产品系列 产品子类比较少的、创建操作比较简单 。
在前面的实例中我们知道,交通工具的实例化过程是经常需要改变的,所以我们将这部分提取到外部来管理,这也就体现了面向对象设计的一个原则,及找出程序中会变化的方面然后将其和固定不变的方面相分离。一种简单的实现方案是利用工厂模式,这里我们用简单工厂模式实现。实例如下:
class TrafficToolFactory
{
public function createTrafficTool($name)
{
switch ($name){
case 'Foot':
return new Foot();
break;
case 'Car':
return new Car();
break;
case 'Bicycle':
return new Bicycle();
break;
default:
exit('set trafficTool error!');
break;
}
}
}
class Student
{
private $trafficTool;
public function __construct($trafficTool)
{
//通过工厂产生依赖的交通工具实例
$factory = new TrafficToolFactory();
$this->trafficTool = $factory->createTrafficTool($trafficTool);
}
public function go_to_school(){
$this->trafficTool->go();
}
}
$student = new Student('Car');
//$student2 = new Student('Foot');
//$student3 = new Student('Bicycle');
$student->go_to_school();
这里我们添加了“交通工具”工厂,在学生实例化的过程中指定需要的交通工具,则工厂生产相应的交通工具实例。在一些简单的情况下,简单工厂模式可以解决这个问题。我们看到学生和交通工具之间的依赖关系没有了,但是却变成学生和交通工具工厂之间的依赖。当需求增加时,我们需要修改简单工厂,如果依赖增多,工厂将十分庞大,依然不利于维护。下一步就是我们今天的主要配角 —— DI
3、DI(Dependency Injection 依赖注入)
本文中提到的一系列依赖,只要不是由内部产生(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DependencyInjection简称DI) 。实例如下:
class Student
{
private $trafficTool;
public function __construct(Go_To_School $trafficTool)
{
$this->trafficTool = $trafficTool;
}
public function go_to_school(){
$this->trafficTool->go();
}
}
$car = new Car();
$student = new Student($car);
$student->go_to_school();
现在初始化学生类时提供的参数必须是Go_To_School接口类的一个实例,即通过依赖注入的方式解决依赖问题,否则就会提示出错。这里要注意,依赖注入要以接口的形式进行限制,不能随意开放
4、IoC容器(Inversion of Control 控制反转)
IoC是将设计好的类交给系统去控制,而不是在类内部控制。这称为控制反转。上例中我们是通过手动注入依赖,而IoC容器实现了依赖的自动注入。下面给出一个简化版的例子。里面涉及到反射机制,网上有很多关于反射机制的讲解,读者可以自行查找,这里就不细说了。
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');
}
//默认生成实例的回调函数
protected function getClosure($abstract, $concrete) {
return function($container) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
return $container->$method($concrete);
};
}
//解决接口和要实例化类之间的依赖关系
public function make($abstract) {
$concrete = $this->getConcrete($abstract);
if($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
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) {
if($concrete instanceof Closure) {
return $concrete($this);
}
$reflector = new ReflectionClass($concrete);
if(!$reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable";
}
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
//解决通过反射机制实例化对象时的依赖
protected function getDependencies($parameters) {
$dependencies = [];
foreach($parameters as $parameter) {
$dependency = $parameter->getClass();
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);
}
}
这就是简化版的IoC容器类,使用bind()函数进行服务绑定,使用make()函数来进行解析,最后在容器内由build()函数创建并返回实例。下面是具体如何使用
//实例化IOC容器
$ioc = new Container();
//填充容器
$ioc->bind('Go_To_School', 'Car'); //第一个参数'Go_To_School'是接口,第二个参数'Car'是交通工具类
$ioc->bind('student', 'Student'); //第一个参数'student'可以理解为服务别名,用make()实例化的时候直接使用别名即可,第二个参数'Student'是学生类
//通过容器实现依赖注入,完成类的实例化
$student = $ioc->make('student');
$student->go_to_school();
填充容器的时候也可以直接绑定自定义的回调函数:
$ioc->bind('Go_To_School', function (){
return new Car();
});
现在,我们不仅解除了学生类与交通工具类的依赖关系,而且容器类没有和他们产生任何依赖。我们通过注册、绑定(bind)的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为创建一个类的实例的方法,只有在真正的创建(make)操作被调用执行时,才会触发。这样一种方式,使得我们更容易在创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个回调即可。例如现在我们想步行去学校,只要再绑定$ioc->bind('Go_To_School',
'Foot');就可以了。用何种方式去学校,我们可以自由的选择。
通过上述例子可以看到IOC容器最核心的功能,解决依赖注入的根本问题。在实现过程中,没有用new关键字来实例化对象,不需要人来关注组件之间的依赖关系,只要在容器填充过程中理顺接口和实现类之间的关系及实现类与依赖接口之间的关系就可以流水线式的完成实现类的实例化过程。
好了,分享到此就进入尾声了,如有错误,欢迎指正!
laravel 服务容器实例——深入理解IoC模式的更多相关文章
- Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)
容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...
- Laravel 服务容器、服务提供器、契约实例讲解
前言 刚开始看laravel服务容器.契约.服务提供器的确生涩难懂,不单单是概念繁多,而且实际的demo很难找(找是找到了,但难用啊),最后就隔一段时间看一遍,大概个十来遍,还真给看出个门道, ...
- laravel服务容器
laravel框架底层解析 本文参考陈昊<Laravel框架关键技术解析>,搭建一个属于自己的简化版服务容器.其中涉及到反射.自动加载,还是需要去了解一下. laravel服务容器 建立项 ...
- Laravel服务容器的绑定与解析
本篇文章给大家带来的内容是关于Laravel服务容器的绑定与解析,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言 老实说,第一次老大让我看laravel框架手册的那天早上,我 ...
- laravel服务容器 转
laravel框架底层解析 本文参考陈昊<Laravel框架关键技术解析>,搭建一个属于自己的简化版服务容器.其中涉及到反射.自动加载,还是需要去了解一下. laravel服务容器 建立项 ...
- 关于使用 Laravel 服务容器的优势介绍
如果说laravel框架的核心是什么,那么无疑是服务容器.理解服务容器的概念,对于我们使用laravel太重要了,应该说是否理解服务容器的概念是区分是否入门laravel的重要条件.因为整个框架正是在 ...
- laravel服务容器-----深入理解控制反转(IoC)和依赖注入(DI)
首先大家想一想什么是容器,字面意思就是盛放东西的东西,常见的变量,对象属性都是容器,一个容器能够装什么东西,完全在于你对这个容器的定义.有的容器不仅仅只是存文本,变量,而是对象,属性,那么我们通过这种 ...
- laravel服务容器(IOC控制反转,DI依赖注入),服务提供者,门脸模式
laravel的核心思想: 服务容器: 容器:就是装东西的,laravel就是一个个的对象 放入:叫绑定 拿出:解析 使用容器的目的:这里面讲到的是IOC控制反转,主要是靠第三方来处理具体依赖关系的解 ...
- Laravel 服务容器,IoC,DI
DI DI 就是常说的依赖注入,那么究竟什么是依赖注入呢? 打个比方,电脑(非笔记本哈)需要键盘和鼠标我们才能进行操作,这个‘需要’换句话说就是‘依赖’键盘和鼠标. 那么,相应的,一个类需要另一个类才 ...
随机推荐
- 重写Object的equals方法
Object的equals比较两个对象是否相同,没有重写时比较的是内存地址是否相同(==). 但我们有时候比较的是两个对象中的属性是否相同, 重写equals: package cn.sasa.dem ...
- Code once, debug everywhere.
1.通常语言调用一个函数会出exception的情况,在javascript里面返回的是undefined.等到程序运行不正常的时候,你看到数据结构的有些地方为什么是undefined,只能哭了. 2 ...
- Mybatis返回值类型是hashmap,输入键值对为空时,key 丢失的问题
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...
- 20170720 Celery 异步任务处理到Sql Server 发生死锁
-- 1. 异常提示情况如下: 需要解决为什么引起死锁 -- 叹气 原因: 在使用Celery 启用了 配置参数 CELERYD_CONCURRENCY = 10 表示开了10块线程池. 有好处, ...
- docker基本原理
写的很不错的文章,作个存档 什么是容器 容器是 种轻量级.可移植的为应用程序提供了隔离的运行空间 .每个容器内都包含一个独享的完整用户环境,并且 个容器内的环境变动不会影响其他容器的运行环境,可以使应 ...
- error: Microsoft Visual C++ 14.0 is required(line_profiler模块安装失败的解决办法)
一.我的安装环境: 1.系统:win10,64位 2.python版本:python3.6.4 二.遇到的问题: 1.cmd黑屏终端下输入命令:pip install line_profiler(安装 ...
- 两种ps切图方法(图层/切片)
两种Ps切图方法 一. 基础操作: a) Ctrl++ 放大图片,ctrl - -缩小图片 b) 按住空格键space+,点击鼠标左键,拖动图片. c) 修改单位,点击编辑 ...
- Spark log4j日志配置详解(转载)
一.spark job日志介绍 spark中提供了log4j的方式记录日志.可以在$SPARK_HOME/conf/下,将 log4j.properties.template 文件copy为 l ...
- shell 中 标准输出和错误输出
命令 标准输出 标准错误 >/dev/null 2>&1 丢弃 丢弃 2>&1 >/dev/null 丢弃 屏幕 1>/dev/null 丢弃 屏幕 2& ...
- Http post/get
什么是HTTP? 超文本传输协议(HyperText Transfer Protocol -- HTTP)是一个设计来使客户端和服务器顺利进行通讯的协议. HTTP在客户端和服务器之间以request ...