Whip Monstrous Code Into Shape 14-Consider Fluent Interfaces

Fluent interfaces often get a bad rap, but the truth is that there are countless scenarios where you may consider leveraging them. In this episode, we'll review the basic definition and makeup for a fluent API, and will then move on discussing a handful of real-life examples in the wild.


这个视频讲的是使用 Fluent interface。Fluent interface 是一种 api 设计模式,简单的讲就是通过 return self 来达到“链式调用”的效果。这种方式最初的目的是为了"provide more readable code",写出可读性更好的代码,但是事实真的如此吗?在视频开头 jeff 找了一个反对者的博客,题目就是 “Fluent Interfaces are Evil”。Fluent interface 真的是 evil 吗?工具不是 evil,关键是如何使用。

Laravel 中很多地方都使用了 Fluent interface。比如 TestCaseQuery\BuilderPipeline 以及 laravel-elixir

TestCase:

public function testExample()
{
    $this->get('/')
        ->see('Laravel');
}

Query\Builder:

$queryBuilder
    ->select('u')
    ->from('User u')
    ->where('u.id = :identifier')
    ->orderBy('u.name', 'ASC')
    ->setParameter('identifier', 100);

Pipeline:

$this->pipeline
    ->send($command)
    ->through($this->pipes)
    ->then($callback);

laravel-elixir:

elixir((mix) => {
    mix.sass('app.scss')
        .version('css/styles.css')
       .webpack('app.js');
});

从这些调用来看,一定程度上使代码可读性变得更好,而且看起来和现实生活比较贴近。比如第一个例子,现实生活的逻辑就是访问一个路由,然后看到某个字符串,相比较下面这种调用更容易理解。

public function testExample()
{
    $result = $this->call('get', '/');

    $this->assertContains('Laravel', $result);
}

实现一个 Fluent interface 也很简单,每个方法调用完成后返回实例本身就行。

class AuthenticateUser
{
    public function invite()
    {
        $this->validateRequest()
            ->createToken()
            ->send();
    }

    private function validateRequest()
    {
        // do something;
        return $this;
    }

    private function createToken()
    {
        // do something;
        return $this;
    }

    private function send()
    {
        // do something;
        return $this;
    }
}

这种写法也有一种弊端,就是没有办法控制调用的次序,比如下面这种调用方式肯定不会出错,但是逻辑上就有很大的问题:

$this->createToken()
    ->send()
    ->validateRequest();

解决办法是将不同种类的功能抽象到不同类中,方法内部不再使用return this,取而代之的是return 下一个处理类。

class AuthenticateUser
{
    public function invite()
    {
        $this->validateRequest()
            ->createToken()
            ->send();
    }

    private function validateRequest()
    {
        // do something;
        return new TokenCreator();
    }
}

class TokenCreator
{
    public function createToken()
    {
        //...
        return new MailSender();
    }
}

class MailSender
{
    public function send()
    {
        return $this;
    }
}

如果你想大量使用这种方法的话,务必要读一读这篇博客:“Fluent Interfaces are Evil”,在博客里面作者分析了很多弊端,比如:

  • Fluent Interfaces break Encapsulation 破坏封装性;
  • Fluent Interfaces break Decorators (and sometimes Composition) 破坏装饰器(基于作者自己的包,因为运用了大量的装饰器);
  • Fluent Interfaces are harder to Mock 测试的时候很难 Mock;
  • Fluent Interfaces make diffs harder to read 针对滥用的情况,可能使得代码阅读起来更加困难(里面有个极端例子,Fluent Interface 调用起来很爽,所以写了一个几十行的调用);
  • Fluent Interfaces are less readable (personal feeling) 作者个人看法,觉得使得代码可读性更差;
  • Fluent Interfaces cause BC breaks during early development stages 在早期开发阶段就会导致无法向后兼容;

每种方法可能有便利之处,也会有弊端,没必要一味的拒绝,也没有必要在每个项目中都把这些方法用一遍。把这些方法当作是一些 tools,在合适的时候使用合适的方法,说不定会让你事半功倍。

本帖已被设为精华帖!
本帖由系统于 7年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 2
Lonexw

刚好最近的有个项目在考虑要不要采用这样的实现,我去详细研究一下

7年前 评论
nickfan

Fluent模式其实更适合细分功能节点,组合起来调用不封一层的话重构起来会比较痛苦,但每次链式调用都封个方法也挺累的,换句话说,暴露流程细节,可读性强,重构累。

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!