【发布时间】:2010-12-10 00:13:04
【问题描述】:
如何创建一个自然流畅的 API?
这主要是使用扩展方法吗?
【问题讨论】:
如何创建一个自然流畅的 API?
这主要是使用扩展方法吗?
【问题讨论】:
This article 比我能解释得更好。
编辑,不能在评论中挤这个...
接口有两个方面,实现和使用。在创建方面还有更多工作要做,我同意这一点,但是主要的好处可以在事物的使用方面找到。事实上,对我来说,流畅接口的主要优点是更自然、更容易记住和使用,为什么不呢,更美观的 API。只是也许,必须以流畅的形式压缩 API 的努力可能会导致更好地考虑 API?
正如 Martin Fowler 在the original article about fluent interfaces 中所说:
可能是最重要的 关于这种风格的注意是 意图是做一些事情 内部线 领域特定语言。确实这是 为什么我们选择“流利”这个词来 描述它,在很多方面两者 术语是同义词。 API 是 主要设计为可读性和 流动。这种流畅的代价是 更多的努力,在思考和 API 构造本身。这 构造函数、setter 和 添加方法更容易 写。想出一口流利的流利 API 需要深思熟虑。
在大多数情况下,API 会被创建一次并被反复使用,因此付出额外的努力可能是值得的。
而且很冗长?如果它有助于程序的可读性,我完全赞成冗长。
【讨论】:
先生,
虽然您可以编写扩展方法来编写流畅的界面,但更好的方法是使用构建器模式。我和你在同一条船上,我正在尝试找出流畅界面的一些高级功能。
您将在下面看到我在another thread 中创建的一些示例代码
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);
【讨论】:
WithFoo 方法),并清楚地表明只设置了属性。我认为只有在需要副作用时才应使用“流利”样式。
虽然许多人认为 Martin Fowler 是流畅 API 讨论中的杰出代表,但他早期的设计主张实际上是围绕 fluent builder pattern 或 method chaining 发展而来的。 Fluent API 可以进一步演变为实际的internal domain-specific languages。一篇文章解释了如何将语法的 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 中大量使用,这是一种流畅的 API / 内部特定领域的语言,用 Java 建模 SQL 语言
【讨论】:
这是一个很老的问题,这个答案可能应该是评论而不是答案,但我认为这是一个值得继续讨论的话题,而且这个回复太长了,不能成为评论。
关于“流畅性”的最初想法似乎基本上是关于向对象添加功能和灵活性(方法链等),同时使代码更加不言自明。
例如
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(在我想象的假设情况下)完成和失败是对原始 Ajax 对象的添加,并且能够在不更改任何原始 Ajax 对象代码或任何现有代码的情况下添加使用原始的 Ajax 对象,并且不会创建一次性的东西,这些东西是代码的一般组织的例外。
所以我确实发现了让对象函数的子集返回“this”对象的价值。事实上,每当我有一个会返回 void 的函数时,我都会考虑让它返回 this。
但我还没有真正发现向对象添加“流畅接口”(例如“Set”)的重要价值,尽管理论上似乎可能会出现一种类似名称空间的代码组织这样做的做法,这可能是值得的。 (“Set”可能不是特别有价值,但“Command”、“Query”和“Transfer”可能,如果它有助于组织事物并促进和最小化添加和更改的影响。)这种做法的潜在好处之一,这取决于它是如何完成的,这可能会提高编码人员的典型护理水平和对保护水平的关注——缺乏这些肯定会导致大量的悲痛。
【讨论】:
KISS:保持简单愚蠢。
Fluent 设计是关于整个 API 中使用的一种美学设计原则。您在 API 中使用的方法可能会略有变化,但通常最好保持一致。
即使您可能认为“每个人都可以使用此 API,因为它使用所有不同类型的方法”。事实上,用户会开始感到迷茫,因为您不断地将 API 的结构/数据结构更改为新的设计原则或命名约定。
如果您希望中途更改为不同的设计原则,例如.. 从错误代码转换为异常处理,因为某些更高的命令能力。这将是愚蠢的,通常会带来很多痛苦。最好坚持到底并添加客户可以使用和销售的功能,而不是让他们重新编写和重新发现他们的所有问题。
从以上内容可以看出,编写 Fluent API 的工作远不止眼前一亮。在开始写作之前,需要做出心理和审美选择,即便如此,满足客户需求并保持一致的感觉、需求和愿望也是最难的。
【讨论】:
什么是流畅的 API
维基百科在这里定义它们http://en.wikipedia.org/wiki/Fluent_interface
为什么不使用流畅的界面
我建议不要实现传统的流畅接口,因为它会增加您需要编写的代码量,使您的代码复杂化并且只是添加不必要的样板。
另一种选择,什么都不做!
不要实现任何东西。不要为设置属性提供“简单”的构造函数,也不要提供一个聪明的界面来帮助您的客户。允许客户端以通常的方式设置属性。在 .Net C# 或 VB 中,这可以像使用 object initializers 一样简单。
Car myCar = new Car { Name = "Chevrolet Corvette", Color = Color.Yellow };
所以你不需要在你的代码中创建任何巧妙的接口,而且可读性很强。
如果您有非常复杂的 Sets 属性必须设置或按特定顺序设置,则使用单独的配置对象并通过单独的属性将其传递给类。
CarConfig conf = new CarConfig { Color = Color.Yellow, Fabric = Fabric.Leather };
Car myCar = new Car { Config = conf };
【讨论】:
不,是的。基础是一个或多个您想要流畅运行的类型的良好接口。具有扩展方法的库可以扩展此行为并返回接口。扩展方法使其他人可以使用更多方法扩展您的 fluent API。
良好的流畅设计可能很难,并且需要相当长的试错期才能完全微调基本构建块。只是一个用于配置或设置的流畅 API 并不难。
通过查看现有 API 来学习构建流畅的 API。将 FluentNHibernate 与 fluent .NET API 或 ICriteria fluent 接口进行比较。许多配置 API 的设计也很“流畅”。
【讨论】:
使用流畅的 API:
myCar.SetColor(Color.Blue).SetName("Aston Martin");
【讨论】:
编写流畅的 API 很复杂,这就是为什么我编写了 Diezel,它是 Java 的流畅 API 生成器。它生成带有接口(或课程)的 API,用于:
它还生成实现。
这是一个 Maven 插件。
【讨论】: