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));
    }

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

4

8 回答 8

51

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

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

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

方法链

方法链接背后的关键见解是永远不会有返回 void 的方法,而是始终返回一些对象,或者更常见的是返回一些接口,以便进行进一步的调用。

您不一定需要返回调用该方法的同一对象 - 也就是说,您并不总是需要“返回 this;”。

一种有用的设计技术是创建一个内部类——我总是在它们后面加上“Expression”——它公开了流式 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);
于 2008-11-16T06:32:38.080 回答
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");
于 2008-11-16T02:29:24.623 回答
16
于 2008-11-16T02:28:42.020 回答
5

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

在以下位置查看:

C# 流利界面设计指南第 1 部分

我有一个关于 Chaining X Nesting 的部分,你可能会很感兴趣。

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

最好的祝福,

安德烈·维安娜

于 2010-08-05T01:30:53.320 回答
3

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

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

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

这样,任何实现接口的具体类都可以获得流畅的方法链接能力。FWIW .. 我用 C# 1.1 编写了上面的代码

你会发现这种技术在 jQuery API 中随处可见

于 2008-11-16T02:27:38.120 回答
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,但我从未使用过它

于 2008-11-16T02:28:05.140 回答
1

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

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

  2. 您也许可以使用对象初始化来模拟流畅,但这仅适用于实例化时,并且仅适用于单参数方法(其中属性只是一个设置器)。这对我来说似乎很骇人听闻,但它就在那里。

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

于 2008-11-16T02:30:18.023 回答
0
于 2009-09-15T01:13:12.990 回答