Fluent Interfaces Are Bad for Maintainability

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

流畅接口,最早由马丁·福勒提出并命名,是面向对象编程中一种非常便利的对象通信方式。它使得对象的外观更易于使用和理解。然而,它破坏了对象的内部设计,使得其更加难以维护。马可·皮韦塔在他的博客文章流畅接口是邪恶的中对此发表了一些看法;现在我也要加入我的几分意见。

让我们以我自己的库jcabi-http为例,这是我几年前创建的,当时我认为流畅接口是一件好事。下面是如何使用该库进行HTTP请求并验证其输出:

这种方便的方法链式调用使代码变得简洁明了,对吗?表面上是这样的。但是,包括JdkRequest在内的库的类的内部设计与优雅相去甚远。最大的问题是它们相当庞大,无法扩展而不使它们变得更庞大。

例如,现在JdkRequestmethod()fetch()和其他几个方法。当需要新功能时会发生什么?唯一的方法就是通过添加新方法使类变得更大,这就危及了它的可维护性。例如,在这里我们添加了multipartBody(),在这里我们添加了timeout()

当我在jcabi-http中收到一个新的功能请求时,我总是感到害怕。我明白这很可能意味着在RequestResponse和其他已经臃肿的接口和类中添加新方法。

为了解决这个问题,我确实在库中尝试了一些东西,但并不容易。看看这个.as(RestResponse.class)方法调用。它的作用是用RestResponse装饰一个Response,使其具有更多方法。我只是不想像其他许多库那样使Response包含50多个方法。以下是它的功能(这是伪代码):

正如你所见,我将所有可能的方法都放在了补充装饰器RestResponseJsonResponseXmlResponse和其他装饰器中(点击这里查看)。这样做确实有帮助,但为了使用类型为Response的中心对象编写这些装饰器,我们不得不使用那个“丑陋”的as()方法(点击这里查看),它严重依赖于反射和类型转换。

换句话说,流畅接口意味着要么有庞大的类,要么有一些丑陋的解决办法。我之前在写关于Streams API和接口Stream时就提到了这个问题,这个接口非常流畅,总共有43个方法!

这就是流畅接口最大的问题——它们强制对象变得庞大。

对于使用者来说,流畅接口非常完美,因为所有方法都在一个地方,类的数量非常少。使用它们非常容易,尤其是在大多数集成开发环境中使用代码自动补全。它们还使客户端代码更易读,因为“流畅”结构看起来类似于普通英语(也称为DSL)。

这一切都是正确的!然而,它们给对象设计带来的损害是代价过高。

那么,有什么替代方案呢?

我建议您使用装饰器和智能对象。如果我现在能够重新设计jcabi-http,我会这样做:

这段代码与上面第一个代码段中的代码相同,但它更加面向对象。当然,这段代码的明显问题是,IDE几乎无法自动完成任何操作。此外,我们将不得不记住许多类的名称。对于习惯于流畅接口的人来说,这种结构似乎很难阅读。此外,它与DSL的理念相去甚远。

但是这里有一些好处。首先,每个对象都很小,非常内聚,并且它们都是松耦合的,这在面向对象编程中是明显的优点。其次,向库中添加新功能就像创建一个新类一样简单,不需要修改现有的类。第三,由于类很小,单元测试被简化了。第四,所有的类都可以是不可变的,这在面向对象编程中也是明显的优点。

因此,使用价值和可维护性之间似乎存在着冲突。流畅接口对用户来说是好的,但对库开发人员来说是坏的。小对象对开发人员来说是好的,但很难理解和使用。

如果你习惯于大类和过程式编程,似乎是这样。对我来说,大量的小类似乎是一种优势,而不是一个缺点。在代码内部清晰、简单、可读的库更容易使用,即使我不知道哪个类最适合我。即使没有代码自动完成,我也可以自己找出答案,因为代码是干净的。

此外,我经常发现自己对扩展现有功能非常感兴趣,无论是在我的代码库内部还是通过向库提交拉取请求。如果我知道我引入的更改是隔离和易于测试的,我对这样做的兴趣就更大。

因此,我不再使用流畅接口,只使用对象和装饰器。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-05 at 21:25

sixnines availability badge   GitHub stars