Laravel深入学习1 - 依赖注入
声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。 欢迎转载,转载请注明出处,谢谢! 依赖注入问题所在Laravel框架的基础在于其IoC容器。要想真正了解框架的核心,需要对容器有一定的概念。然而,我们需要注意的是IoC仅是软件设计模式:依赖注入的一种便利的实现形式。容器本身不是依赖注入的必要条件,在框架他只是让其变得更加简便。 首先,让我们探索下为什么依赖注入是有益的。考虑到如下代码中的类和方法: class UserController extends BaseController { public function getIndex() { $users = User::all(); return View::make('users.index',compact('users')); } } 代码简洁易懂,但是在没有连接到数据库的情况下,我们是无法进行测试的。换句话说, Eloquent ORM 被紧密耦合到控制器中了。在未连接数据库的情况下,我们无法测试当前引用了Eloquent ORM的控制器的方法。这段代码同样违背了软件设计原则 关注点分离(SoC) 。简言之:控制器知道的太多。控制器无需知道数据_从何而来_,只需关注如何接入;无需关心数据库在MySQL中是否可用,而只关心数据在_某处_可用。
所以,将web层(controller)从数据层解耦分离出来会是有益的。这在我们对数据进行存储迁移时是有利的,也会使代码的测试更为简单。将“Web”认为是到“真正”应用的传输层。 想象一下,应用是一台有着多种电缆接口的显示器。我们能通过HDMI,VGA或者DVI接入显示功能。也可将应用比喻成你接入互联网的电缆。显示器的主要功能大部分依赖着电缆。而电缆仅仅是一种类似HTTP接入你应用的传输部件。所以我们不想将这部分内容(控制器)和应用逻辑糅合在一块。这种做法可以允许任何传输层,比如API或者移动应用程序来接入我们的应用逻辑。 所以,我们再次注入一个存储类,来代替现有将控制器和Eloquent ORM糅合在一块的做法。 契约式设计
首先,我们定义一个接口和相应的实现: interface UserRepositoryInterface { public function all(); } class DbUserRepository implements UserRepositoryInterface { public function all() { return User::all()->toArray(); } } 接下来,我们向控制器中注入此接口的实现。 class UserController extends BaseController { public function __construct(UserRepositoryInterface $users) { $this->users = $users; } public function getIndex() { $users = $this->users->all(); return View::make('users.index',compact('users')); } } 现在,我们的控制器根本不晓得数据存储在何处,无知是福啊!我们的数据可以来自MySQL,MongoDB,甚至是来自Redis。我们不知道这其中的区别,也不需要关心。仅仅这一点小小的改变,我们就可以将web层从数据层脱离,当然当数据存储改变时也不会影响到我们。
为了巩固上面的知识,我们从一个测试案例开始。首先,模拟一个库并绑定到IoC容器中,然后确保控制器正确的调用了该库: public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('UserRepositoryInterface'); $repository->shouldReceive('all')->once()->andReturn(array('foo')); App::instance('UserRepositoryInterface',$repository); // Act... $response = $this->action('GET','UserController@getIndex'); // Assert... $this->assertResponSEOk(); $this->assertViewHas('users',array('foo')); }
继续深入让我们通过另一个示例来加深对依赖注入的理解。有这样一个场景,我们需要对用户账户中发生的财务变更用进行通知。这里我们定义两个接口,或者叫约定。这些约定将会使需求变更变的很便捷。 interface BillerInterface { public function bill(array $user,$amount); } interface BillingNotifierInterface { public function notify(array $user,$amount); } 紧接着,我们来实现 class StripeBiller implements BillerInterface { public function __construct(BillingNotifierInterface $notifier) { $this->notifier = $notifier; } public function bill(array $user,$amount) { // Bill the user via Stripe... $this->notifier->notify($user,$amount); } } 由于各个类之间已经进行了职责分离,为财务账单(billing)类注入不同的通知程序将会很方便。比如注入短信通知类
那问题来了,怎么实现依赖注入呢? $biller = new StripeBiller(new SmsNotifier); 如上,简单吧,这就是依赖注入。只需要将通知器传入到账单系统,而不用担心通知器的使用。微小的改动就能是代码很清晰,这种清晰的职责界定设计,让我们使代码维护简单,当然也方便模拟测试。 那IoC容器是怎么一回事?依赖注入必须要用到他么?这里当然不是!在以后的章节中,我们会看到IoC容器只是为了更好的组织管理依赖注入,但它并非必须。只要遵循本章中介绍的设计原则,你可以在任何项目中实现依赖注入,也不用管是否有这样一个容器可用。 太多JAVA了吧很多人指责,在PHP中使用接口把代码变的太过冗长,太象“JAVA”。你必须定义一个接口并实现一个类,这得多敲多少代码。 在小而简的项目中,我承认这种批判。这样的项目中,接口是不必要的,因为就你自己用,以后也不会去改。即使架构上牛逼的架构师也会说“需求永远不会确定”,但是需要承认的是,总有tm那么一些地方就是改不着。 接口在大型项目中是非常有用的,这样额外的代码是为了保证未来你代码的灵活性和可测试性。当你快速切换代码实现的时候,一定会闪瞎某些人的狗眼。当然我们的目的是为了让代码能够适应各种操蛋需求的变更。 总之,我们一直提倡“简洁”架构。如果如果你的项目很小,不需要遵循这么多规范,也别不好意思。代码敲的怎么爽怎么来。如果不写接口,也行,以后再说呗,又不是结婚买房,都tm逼的。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |