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。比如 TestCase、Query\Builder、Pipeline 以及 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,在合适的时候使用合适的方法,说不定会让你事半功倍。

本帖已被设为精华帖!
本帖由系统于 9年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 2
Lonexw

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

9年前 评论
nickfan

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

9年前 评论

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