流畅接口 - 方法链

33

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

这里有一个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));
    }

那么其他人如何创建流畅的接口呢?你该如何创建呢?需要哪种语言/平台/技术?


2
你甚至不需要 .NET 2.0 就可以做到这一点。 - Brad Wilson
我刚修改了问题,使其更具语言无关性,因为这个问题并不仅仅针对C#和.NET。 - Chris Pietschmann
可能是在C# 3中编写流畅接口的技巧的重复问题。 - nawfal
8个回答

51
建立流畅接口的核心思想是可读性 - 代码读者应该能够理解正在实现什么,而不必深入代码实现细节。
在像C#、VB.NET和Java等现代面向对象语言中,方法链是实现这一目标的一种方式,但不是唯一的技术 - 另外两种是工厂类和命名参数。
请注意,这些技术并非互斥的 - 目标是最大程度地提高代码的可读性,而不是遵循某种纯粹的方法。 方法链 方法链背后的关键洞见是永远不要有返回void的方法,而是始终返回某个对象或更常见的是一些接口,允许进行进一步的调用。
您不需要一定返回调用方法的同一对象 - 也就是说,您不总是需要“return this;”。
一个有用的设计技巧是创建一个内部类 - 我总是在其后缀加上“Expression” - 它公开了流畅的API,允许配置另一个类。
这有两个优点 - 它将流畅的API保持在一个地方,与类的主要功能隔离开来,并且(因为它是一个内部类),它可以以其他类无法做到的方式调整主类的内部。
您可能需要使用一系列接口,以控制开发人员在某个给定时间点可以使用哪些方法。 工厂类 有时您想要建立一系列相关对象 - 例如,NHibernate标准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);

喜欢你最后一个命名参数的示例! - user65199

50
你可以在任何版本的.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");

16
据我所知,“流畅接口”这个术语并没有指定特定的技术或框架,而是一个设计模式。维基百科上有一个广泛的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");
    }
}

5
不久之前,我也曾有你现在的疑虑。经过一些研究后,我正在撰写一系列关于设计流畅接口技巧的博客文章。
请点击以下链接查看: C#中设计流畅接口的指南-第一部分 我在那里讲了关于链式调用和嵌套的内容,这可能会对你有所帮助。
在接下来的文章中,我将更深入地探讨这个主题。
祝好,
André Vianna

3

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

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

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

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

您将在jQuery API中找到这种技术的大量使用


1

有几件事情可能在.Net 3.5/C# 3.0中实现:

  1. 如果一个对象没有实现流畅的接口,您可以使用扩展方法来链接您的调用。

  2. 您可以使用对象初始化来模拟流畅,但这只适用于实例化时,并且仅适用于单参数方法(其中属性只是一个setter)。这对我来说似乎是一种hackish方法,但这就是它。

个人而言,如果您正在实现构建器对象,我认为使用函数链接没有任何问题。如果构建器对象具有链接方法,则可以使要创建的对象更加清晰。这只是我的想法。


1

这就是我建立所谓流畅接口或者说我的唯一尝试的方式

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 表达式。正如 Brad 指出的那样,你可以在任何版本的 .net 中完成这个任务。虽然我认为 lambda 表达式可以带来更有趣的可能性,比如这个。

======

还有一些好的例子是nHibernate的Criteria API,还有一个流畅的nhibernate扩展用于配置nhibernate,但我从未使用过它。


0

C# 4.0 中的动态关键字将使编写动态样式生成器成为可能。请查看以下有关 JSON 对象构建的文章


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接