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关键字来实例化对象,不需要人来关注组件之间的依赖关系,只要在容器填充过程中理顺接口和实现类之间的关系及实现类与依赖接口之间的关系就可以流水线式的完成实现类的实例化过程。
好了,分享到此就进入尾声了,如有错误,欢迎指正!
【推荐】中国电信天翼云云端翼购节,2核2G云服务器一口价38元/年
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在.NET Core中使用异步多线程高效率的处理大量数据
· 聊一聊 C#前台线程 如何阻塞程序退出
· 几种数据库优化技巧
· 聊一聊坑人的 C# MySql.Data SDK
· 使用 .NET Core 实现一个自定义日志记录器
· 字节豆包,来园广告
· 为什么推荐在 .NET 中使用 YAML 配置文件
· 订单超时自动取消,我们是这样做的。。。
· 在 .NET Core 中使用 Channel 实现生产者消费者模式
· C#字符串拼接的几种方式及其性能分析对比