7

声明:本文并非博主原创,而是来自对《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中是否可用,而只关心数据在_某处_可用。

关注点分离(Separation Of Concerns):

每个类都应有其单一的职责,并且这个职责由这个类完全封装

所以,将web层(controller)从数据层解耦分离出来会是有益的。这在我们对数据进行存储迁移时是有利的,也会使代码的测试更为简单。将“Web”认为是到“真正”应用的传输层。

想象一下,应用是一台有着多种电缆接口的显示器。我们能通过HDMI,VGA或者DVI接入显示功能。也可将应用比喻成你接入互联网的电缆。显示器的主要功能大部分依赖着电缆。而电缆仅仅是一种类似HTTP接入你应用的传输部件。所以我们不想将这部分内容(控制器)和应用逻辑糅合在一块。这种做法可以允许任何传输层,比如API或者移动应用程序来接入我们的应用逻辑。

所以,我们再次注入一个存储类,来代替现有将控制器和Eloquent ORM糅合在一块的做法。

契约式设计

http://www.jdon.com/36303

首先,我们定义一个接口和相应的实现:

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层从数据层脱离,当然当数据存储改变时也不会影响到我们。

服从边限

记得服从职责限定。应用中控制器和路由是HTTP和程序交互的中简介,在大型程序中,不能将他们糅合到你的主要逻辑中。

为了巩固上面的知识,我们从一个测试案例开始。首先,模拟一个库并绑定到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'));
}

你在模仿我么

示例中,我们使用了Mockery模拟库,它提供了一套表述简洁的方法来模仿你的程序。Mockery可通过Composer进行安装。

继续深入

让我们通过另一个示例来加深对依赖注入的理解。有这样一个场景,我们需要对用户账户中发生的财务变更用进行通知。这里我们定义两个接口,或者叫约定。这些约定将会使需求变更变的很便捷。

interface BillerInterface {
    public function bill(array $user, $amount);
}

interface BillingNotifierInterface {
    public function notify(array $user, $amount);
}

紧接着,我们来实现BillerInterface接口:

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)类注入不同的通知程序将会很方便。比如注入短信通知类SmsNotifier或者邮件通知类EmailNotifier。我们的账单系统不需要考虑账单通知的实现,只需要根据约定执加载通知即可。凡是遵守约定的账单,都能实现对用户财务变更的通知。此外,不光我们添加方便,我们也可以单独模拟BillingNotifierInterface接口,来测试账单系统。

善用接口

接口写起来看似添加了很多额外的东西,实际是在加速我们的开发。我们可以在不实现接口的情况下,模拟已开发的接口,来对整个底层逻辑进行测试。

那问题来了,怎么实现依赖注入呢?

$biller = new StripeBiller(new SmsNotifier);

如上,简单吧,这就是依赖注入。只需要将通知器传入到账单系统,而不用担心通知器的使用。微小的改动就能是代码很清晰,这种清晰的职责界定设计,让我们使代码维护简单,当然也方便模拟测试。

那IoC容器是怎么一回事?依赖注入必须要用到他么?这里当然不是!在以后的章节中,我们会看到IoC容器只是为了更好的组织管理依赖注入,但它并非必须。只要遵循本章中介绍的设计原则,你可以在任何项目中实现依赖注入,也不用管是否有这样一个容器可用。

太多JAVA了吧

很多人指责,在PHP中使用接口把代码变的太过冗长,太象“JAVA”。你必须定义一个接口并实现一个类,这得多敲多少代码。

在小而简的项目中,我承认这种批判。这样的项目中,接口是不必要的,因为就你自己用,以后也不会去改。即使架构上牛逼的架构师也会说“需求永远不会确定”,但是需要承认的是,总有tm那么一些地方就是改不着。

接口在大型项目中是非常有用的,这样额外的代码是为了保证未来你代码的灵活性和可测试性。当你快速切换代码实现的时候,一定会闪瞎某些人的狗眼。当然我们的目的是为了让代码能够适应各种操蛋需求的变更。

总之,我们一直提倡“简洁”架构。如果如果你的项目很小,不需要遵循这么多规范,也别不好意思。代码敲的怎么爽怎么来。如果不写接口,也行,以后再说呗,又不是结婚买房,都tm逼的。


laravel
101 声望55 粉丝