流畅的接口 - 方法链接

时间:2008-11-16 02:13:53

标签: language-agnostic design-patterns oop fluent-interface

方法链接是我知道构建流畅接口的唯一方法。

这是C#中的一个例子:

John john = new JohnBuilder()
    .AddSmartCode("c#")
    .WithfluentInterface("Please")
    .ButHow("Dunno");

Assert.IsNotNull(john);

  [Test]
    public void Should_Assign_Due_Date_With_7DayTermsVia_Invoice_Builder()
    {
        DateTime now = DateTime.Now;

        IInvoice invoice = new InvoiceBuilder()
            .IssuedOn(now)
            .WithInvoiceNumber(40)
            .WithPaymentTerms(PaymentTerms.SevenDays)
            .Generate();

        Assert.IsTrue(invoice.DateDue == now.AddDays(7));
    }

那么其他人如何创建流畅的界面。你是如何创造它的?需要什么语言/平台/技术?

8 个答案:

答案 0 :(得分:51)

构建流畅界面背后的核心理念是可读性 - 阅读代码的人应该能够理解正在实现的目标,而无需深入了解实现以澄清细节。

在现代的OO语言中,例如C#,VB.NET和Java,方法链接是实现这一目标的一种方式,但它不是唯一的技术 - 另外两种是工厂类和命名参数。

另请注意,这些技术并不相互排斥 - 目标是最大限度地提高代码的可读性,而不是方法的纯洁性。

方法链接

方法链接背后的关键见解是永远不会有一个返回void的方法,而是总是返回一些对象,或者更经常地返回某个接口,这允许进一步调用。

您无需返回调用该方法的同一对象 - 也就是说,您并不总是需要“返回此内容”。

一种有用的设计技术是创建一个内部类 - 我总是用“表达式”后缀这些 - 暴露了流畅的API,允许配置另一个类。

这有两个好处 - 它将流畅的API保存在一个地方,与类的主要功能隔离,并且(因为它是一个内部类)它可以修改主类的内部,其他类不能修改

您可能希望使用一系列接口来控制在给定时间点开发人员可以使用的方法。

工厂类

有时你想建立一系列相关的对象 - 例子包括NHibernate Criteria API,Rhino.Mocks期望约束和NUnit 2.4的新语法。

在这两种情况下,您都拥有要存储的实际对象,但为了使它们更容易创建,工厂类提供静态方法来制作您需要的实例。

例如,在NUnit 2.4中,您可以写:

Assert.That( result, Is.EqualTo(4));

“Is”类是一个充满工厂方法的静态类,它为NUnit的评估创建约束。

实际上,为了允许舍入误差和浮点数的其他不精确,您可以指定测试的精度:

Assert.That( result, Is.EqualTo(4.0).Within(0.01));

(提前道歉 - 我的语法可能已关闭。)

命名参数

在支持它们的语言(包括Smalltalk和C#4.0)中,命名参数提供了一种在方法调用中包含其他“语法”的方法,从而提高了可读性。

考虑一个假设的Save()方法,它采用文件名,并在保存后应用于该文件的权限:

myDocument.Save("sampleFile.txt", FilePermissions.ReadOnly);

使用命名参数,此方法可能如下所示:

myDocument.Save(file:"SampleFile.txt", permissions:FilePermissions.ReadOnly);

或者,更流利:

myDocument.Save(toFile:"SampleFile.txt", withPermissions:FilePermissions.ReadOnly);

答案 1 :(得分:49)

您可以在任何版本的.NET或任何其他面向对象的语言中创建流畅的界面。您需要做的就是创建一个对象,其方法始终返回对象本身。

例如在C#中:

public class JohnBuilder
{
    public JohnBuilder AddSmartCode(string s)
    {
        // do something
        return this;
    }

    public JohnBuilder WithfluentInterface(string s)
    {
        // do something
        return this;
    }

    public JohnBuilder ButHow(string s)
    {
        // do something
        return this;
    }
}

用法:

John = new JohnBuilder()
    .AddSmartCode("c#")
    .WithfluentInterface("Please")
    .ButHow("Dunno");

答案 2 :(得分:16)

AFAIK,术语“流畅”界面并未指定特定技术或框架,而是指定设计模式。维基百科确实有广泛的example of fluent interfaces in C♯

在简单的setter方法中,您不会返回void而是this。这样,您可以链接该对象上的所有语句,其行为类似。以下是基于原始问题的简单示例:

public class JohnBuilder
{
    private IList<string> languages = new List<string>();
    private IList<string> fluentInterfaces = new List<string>();
    private string butHow = string.Empty;

    public JohnBuilder AddSmartCode(string language)
    {
        this.languages.Add(language);
        return this;
    }

    public JohnBuilder WithFluentInterface(string fluentInterface)
    {
        this.fluentInterfaces.Add(fluentInterface);
        return this;
    }

    public JohnBuilder ButHow(string butHow)
    {
        this.butHow = butHow;
        return this;
    }
}

public static class MyProgram
{
    public static void Main(string[] args)
    {
        JohnBuilder johnBuilder = new JohnBuilder().AddSmartCode("c#").WithFluentInterface("Please").ButHow("Dunno");
    }
}

答案 3 :(得分:5)

前段时间我对你现在的怀疑有同样的疑问。我做了一些研究,现在我正在撰写一系列有关设计流畅界面的技术的博客文章。

请查看:

Guidelines to Fluent Interface design in C# part 1

我有一个关于Chaining X Nesting的部分,对你来说很有意思。

在接下来的文章中,我将以更深入的方式讨论它。

致以最诚挚的问候,

AndréVianna

答案 4 :(得分:3)

通过始终从方法返回包含该方法的相同接口,在面向对象编程中实现Fluent接口。因此,无论版本如何,您都可以在java,javascript和其他喜欢的面向对象语言中实现此效果。

通过使用接口,我发现这种技术最容易实现:

public interface IFoo
{
    IFoo SetBar(string s);
    IFoo DoStuff();
    IFoo SetColor(Color c);
}

通过这种方式,任何实现接口的具体类都可以获得流畅的方法链接功能。 FWIW ..我在C#1.1中编写了上述代码

你会发现这种技术遍布整个jQuery API

答案 5 :(得分:1)

这就是我如何建立我所谓的流利界面或我唯一的forary进入它

Tokenizer<Bid> tkn = new Tokenizer<Bid>();
tkn.Add(Token.LambdaToken<Bid>("<YourFullName>", b => Util.CurrentUser.FullName))
    .Add(Token.LambdaToken<Bid>("<WalkthroughDate>",
          b => b.WalkThroughDate.ToShortDateString()))
    .Add(Token.LambdaToken<Bid>("<ContactFullName>", b => b.Contact.FullName))
    .Cache("Bid")
    .SetPattern(@"<\w+>");

我的例子需要.net 3.5但这只是我lambda的原因。正如布拉德指出的那样,你可以在任何版本的.net中做到这一点。虽然我认为lambda会带来更多有趣的可能性。

==

其他一些很好的例子是nHibernate的Criteria API,还有一个流畅的nhibernate扩展来配置nhibernate,但我从来没用过它

答案 6 :(得分:1)

在.Net 3.5 / C#3.0中可以想到几件事:

  1. 如果对象没有实现流畅的界面,您可以使用扩展方法来链接您的呼叫。

  2. 您可以使用对象初始化来模拟流畅,但这仅适用于实例化时间,并且仅适用于单个参数方法(其中属性仅为setter)。这对我来说似乎很苛刻,但就在那里。

  3. 就个人而言,如果您正在实现构建器对象,我认为使用函数链没有任何问题。如果构建器对象具有链接方法,则会使您创建的对象保持干净。只是一个想法。

答案 7 :(得分:0)

C#4.0中的动态关键字可以编写动态样式构建器。看一下关于JSON对象构造的article