创建流畅的API

时间:2009-10-26 00:56:39

标签: c# fluent

如何创建一个流畅的API?

这主要使用扩展方法吗?

9 个答案:

答案 0 :(得分:44)

This article比我更好地解释了它。

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

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

正如Martin Fowler在the original article about fluent interfaces中所说:

  

可能是最重要的事情   关于这种风格的通知是   意图是做一些事情   内部的线条   DomainSpecificLanguage。的确如此   为什么我们选择“流利”一词来   用两种方式描述它   术语是同义词。 API是   主要设计为可读和   流动。这种流畅的代价是   更多的努力,无论是在思考还是在思考   API构造本身。该   构造函数,setter和的简单API   添加方法要容易得多   写。想起来很流利   API需要经过深思熟虑。

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

又详细?如果它能够提供程序的可读性,那么我只是为了冗长。

答案 1 :(得分:35)

MrBlah,

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

下面您将看到我在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);

答案 2 :(得分:19)

虽然很多人都认为Martin Fowler在流畅的API讨论中是一位杰出的代表,但他早期的设计主张实际上是围绕fluent builder patternmethod chaining发展的。流畅的API可以进一步演变为实际的internal domain-specific languages。这篇文章解释了如何将BNF语法符号手动转换为“流畅的API”:

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

它改变了这个语法:

enter image description here

进入这个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中得到了大量使用,{{3}}是一种流畅的API /内部域特定语言,用Java模拟SQL语言

答案 3 :(得分:8)

这是一个非常古老的问题,这个答案可能应该是一个评论而不是一个答案,但我认为这是一个值得继续讨论的话题,这个答案太长了,无法作出评论。

关于“流畅性”的原始思考似乎基本上是在为对象增加功能和灵活性(方法链接等),同时使代码更加不言自明。

例如

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 - 嵌入在对象层中的代码膨胀会产生与代码消耗这些对象的代码膨胀一样多(如果不是更多)的问题。

使用最后一个版本意味着您只需添加一行非常简单的代码即可向公司类添加(可能有用的)属性。

这并不是说我觉得没有方法链接的地方。我真的很喜欢能够做(像JavaScript)

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

..其中(在我想象的假设情况下)Done和Fail是原始Ajax对象的添加,并且能够在不更改任何原始Ajax对象代码或任何现有代码的情况下添加使用原始的Ajax对象,而不创建对代码的一般组织例外的一次性事物。

所以我确实发现了使对象函数的子集返回'this'对象的价值。事实上,每当我有一个否则会返回void的函数时,我会考虑让它返回。

但我还没有真正发现将“流畅的接口”(.eg“Set”)添加到对象中的重要价值,尽管理论上看起来可能会出现类似命名空间的代码组织。这样做的做法,这可能是值得的。 (“设置”可能不是特别有价值,但“命令”,“查询”和“转移”可能,如果它有助于组织事物并促进和最小化添加和更改的影响。)这种做法的潜在好处之一,取决于它是如何完成的,可能是编码器典型的护理水平和对保护水平的关注 - 这种缺乏肯定会引起巨大的悲伤。

答案 4 :(得分:6)

KISS:保持简单愚蠢。

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

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

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

根据上述内容,您可以看到编写Fluent API的工作量远远超出了人们的需求。在开始写一篇文章之前,有一些心理和美学选择,即便如此,感觉,需求和愿望要符合客户需求并保持一致是最难的。

答案 5 :(得分:5)

什么是流畅的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 };

答案 6 :(得分:1)

使用流畅的API:

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

观看此视频http://www.viddler.com/explore/dcazzulino/videos/8/

答案 7 :(得分:1)

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

  1. 控制呼叫流程
  2. 捕捉泛型类型(如guice one)
  3. 它还会生成实现。

    这是一个maven插件。

答案 8 :(得分:0)

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

良好的流畅设计可能很难,并且需要相当长的试验和错误时间来完全微调基本构建块。只需一个流畅的配置或设置API就不那么难了。

通过查看现有API,学习构建流畅的API可以做到这一点。将FluentNHibernate与流畅的.NET API或ICriteria流畅的界面进行比较。许多配置API也是“流利地”设计的。