56

如何创建一个自然流畅的 API?

这主要使用扩展方法吗?

4

9 回答 9

46

这篇文章比我以往任何时候都更好地解释了它。

编辑,不能在评论中挤这个...

接口有两个方面,实现和使用。在创建方面还有更多工作要做,我同意这一点,但是主要的好处可以在事物的使用方面找到。事实上,对我来说,流畅接口的主要优点是更自然、更容易记住和使用,为什么不呢,更美观的 API。也许,必须以流畅的形式压缩 API 的努力可能会导致更好地考虑 API?

正如 Martin Fowler 在关于流畅接口的原始文章中所说:

关于这种风格,最需要注意的可能是其意图是按照内部 DomainSpecificLanguage 的方式做一些事情。事实上,这就是我们选择“流利”一词来描述它的原因,在许多方面,这两个术语是同义词。API 主要设计为可读和流动的。这种流畅性的代价是更多的努力,无论是在思维上还是在 API 构建本身上。构造函数、setter 和添加方法的简单 API 更容易编写。想出一个好的流畅的 API 需要好好思考。

在大多数情况下,API 被创建一次并被反复使用,额外的努力可能是值得的。

并且冗长?如果它服务于程序的可读性,我完全赞成冗长。

于 2009-10-26T01:00:24.123 回答
37

布拉先生,

虽然您可以编写扩展方法来编写流畅的界面,但更好的方法是使用构建器模式。我和你在同一条船上,我正在尝试找出流畅界面的一些高级功能。

下面你会看到我在另一个线程中创建的一些示例代码

public class Coffee
{
    private bool _cream;
    private int _ounces;

    public static Coffee Make { get { return new Coffee(); } }

    public Coffee WithCream()
    {
        _cream = true;
        return this;
    }
    public Coffee WithOuncesToServe(int ounces)
    {
        _ounces = ounces;
        return this;
    }
}

var myMorningCoffee = Coffee.Make.WithCream().WithOuncesToServe(16);
于 2009-11-25T06:28:35.960 回答
21

虽然许多人认为 Martin Fowler 是 fluent API 讨论中的杰出代表,但他早期的设计主张实际上是围绕fluent 构建器模式方法链发展而来的。Fluent API 可以进一步演变为实际的内部特定领域语言。可以在此处查看解释如何将语法的 BNF 表示法手动转换为“流式 API”的文章:

http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/

它转换了这个语法:

在此处输入图像描述

进入这个 Java API:

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {
  void end();
}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow
// for repetitions. Repetitions can be ended any time because this 
// interface extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Java 和 C# 有点相似,该示例当然也适用于您的用例。上述技术已在 jOOQ 中大量使用,jOOQ是一种流畅的 API / 内部特定于领域的语言,对 Java 中的 SQL 语言进行建模

于 2013-09-09T06:12:55.690 回答
9

这是一个很老的问题,这个答案可能应该是评论而不是答案,但我认为这是一个值得继续谈论的话题,而且这个回复太长了,不能成为评论。

关于“流畅性”的最初想法似乎基本上是关于向对象添加功能和灵活性(方法链接等),同时使代码更加不言自明。

例如

Company a = new Company("Calamaz Holding Corp");
Person p = new Person("Clapper", 113, 24, "Frank");
Company c = new Company(a, 'Floridex', p, 1973);

不如“流利”

Company c = new Company().Set
    .Name("Floridex");
    .Manager(
        new Person().Set.FirstName("Frank").LastName("Clapper").Awards(24)
    )
    .YearFounded(1973)
    .ParentCompany(
        new Company().Set.Name("Calamaz Holding Corp")
    )
;

但对我来说,后者实际上并不比后者更强大、更灵活或不言自明

Company c = new Company(){
   Name = "Floridex",
   Manager = new Person(){ FirstName="Frank", LastName="Clapper", Awards=24 },
   YearFounded = 1973,
   ParentCompany = new Company(){ Name="Calamaz Holding Corp." }
};

..事实上,我认为最后一个版本比前一个版本更容易创建、阅读和维护,而且我会说它在幕后需要的包袱也大大减少。这对我来说很重要,至少有两个原因:

1 - 与创建和维护对象层相关的成本(不管是谁做的)与创建和维护使用它们的代码相关的成本一样真实、相关和重要。

2 - 嵌入对象层中的代码膨胀会产生与使用这些对象的代码中的代码膨胀一样多(如果不是更多)的问题。

使用最后一个版本意味着您可以通过添加一个非常简单的代码行来向 Company 类添加一个(可能有用的)属性。

这并不是说我觉得没有方法链接的地方。我真的很喜欢能够做类似的事情(在 JavaScript 中)

var _this = this;
Ajax.Call({
    url: '/service/getproduct',
    parameters: {productId: productId},
)
.Done(
    function(product){
        _this.showProduct(product);
    }
)
.Fail(
    function(error){
        _this.presentError(error);
    }
);

..where(在我想象的假设情况下)Done 和 Fail 是对原始 Ajax 对象的添加,并且能够在不更改任何原始 Ajax 对象代码或任何现有代码的情况下添加原始的 Ajax 对象,并且没有创建一次性的东西,这些东西是代码的一般组织的例外。

因此,我确实发现了让对象函数的子集返回“this”对象的价值。事实上,每当我有一个会返回 void 的函数时,我都会考虑让它返回 this。

但是我还没有真正发现将“流利的接口”(例如“Set”)添加到对象中的重要价值,尽管从理论上讲,这种做法似乎可能会产生一种类似名称空间的代码组织这样做,这可能是值得的。(“Set”可能不是特别有价值,但“Command”、“Query”和“Transfer”可能,如果它有助于组织事物并促进和最小化添加和更改的影响。)这种做法的潜在好处之一,取决于它是如何完成的,可能会提高编码人员的典型关心水平和对保护水平的关注——缺乏这些肯定会导致大量的悲痛。

于 2013-09-25T22:33:00.637 回答
6

KISS:保持简单愚蠢。

流畅的设计是关于整个 API 中使用的一种美学设计原则。您在 API 中使用的方法可能会略有变化,但通常最好保持一致。

即使您可能认为“每个人都可以使用此 API,因为它使用所有不同类型的方法论”。事实上,用户会开始感到迷茫,因为您不断地将 API 的结构/数据结构更改为新的设计原则或命名约定。

如果您希望中途更改为不同的设计原则,例如......从错误代码转换为异常处理,因为一些更高的命令能力。这将是愚蠢的,通常会带来很多痛苦。最好坚持到底并添加客户可以使用和销售的功能,而不是让他们重新编写和重新发现他们的所有问题。

从以上内容可以看出,编写 Fluent API 的工作远不止眼前一亮。在开始写作之前,需要做出心理和审美选择,即便如此,满足客户需求并保持一致的感觉、需求和愿望也是最难的。

于 2009-10-26T01:44:14.473 回答
5

什么是流畅的 API

维基百科在这里定义它们http://en.wikipedia.org/wiki/Fluent_interface

为什么不使用流畅的界面

我建议不要实现传统的流利界面,因为它会增加您需要编写的代码量,使您的代码复杂化并且只是添加不必要的样板。

另一种选择,什么都不做!

不要实施任何东西。不要为设置属性提供“简单”的构造函数,也不要提供一个聪明的界面来帮助您的客户。允许客户端以通常的方式设置属性。在 .Net C# 或 VB 中,这可以像使用对象初始化器一样简单。

Car myCar = new Car { Name = "Chevrolet Corvette", Color = Color.Yellow };

所以你不需要在你的代码中创建任何聪明的接口,而且这是非常可读的。

如果您有非常复杂的必须设置或按特定顺序设置的属性集,则使用单独的配置对象并通过单独的属性将其传递给类。

CarConfig conf = new CarConfig { Color = Color.Yellow, Fabric = Fabric.Leather };
Car myCar = new Car { Config = conf };
于 2009-10-26T09:57:24.290 回答
1

不,是的。基础是您想要流畅运行的类型的一个或多个良好接口。具有扩展方法的库可以扩展此行为并返回接口。扩展方法使其他人可以使用更多方法扩展您的流畅 API。

一个好的流畅设计可能很难,并且需要相当长的试错期才能完全微调基本构建块。只是一个用于配置或设置的流利的 API 并不难。

通过查看现有 API 来学习构建流畅的 API。将 FluentNHibernate 与 fluent .NET API 或 ICriteria fluent 接口进行比较。许多配置 API 也是“流畅”设计的。

于 2009-10-26T01:03:34.393 回答
1

使用流畅的 API:

myCar.SetColor(Color.Blue).SetName("Aston Martin");

看看这个视频http://www.viddler.com/explore/dcazzulino/videos/8/

于 2010-02-01T19:01:10.023 回答
1

编写流畅的 API 很复杂,这就是为什么我编写了 Diezel,它是 Java 的流畅 API 生成器。它生成带有接口(或课程)的 API,用于:

  1. 控制调用流程
  2. 捕获泛型类型(如 guice 一)

它还生成实现。

这是一个maven插件。

于 2011-12-17T22:16:37.043 回答