Whip Monstrous Code Into Shape 14-Consider Fluent Interfaces

分享 oustn ⋅ 于 2016-12-26 17:41:50 ⋅ 最后回复由 nickfan 2016-12-27 11:38:52 ⋅ 319 阅读

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,在合适的时候使用合适的方法,说不定会让你事半功倍。

Oustn

本帖已被设为精华帖!
本帖由 JobsLong 于 3周前 加精
回复数量: 2
  • JobsLong MOD 那么,下一步的行动是什么?
    2016-12-26 19:47:39

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

  • nickfan 乐天知命,随遇而安~
    2016-12-27 11:38:52

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

暂无评论~~
  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
Ctrl+Enter