【问题标题】:Fluent Interfaces - Method Chaining流利的接口 - 方法链
【发布时间】:2021-02-22 05:36:52
【问题描述】:

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

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

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

【问题讨论】:

  • 您甚至不需要 .NET 2.0 来执行此操作。
  • 我刚刚修改了这个问题,使其更加与语言无关,因为这个问题确实不仅仅针对 C# 和 .NET。

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


【解决方案1】:

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

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

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

方法链

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

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

一种有用的设计技术是创建一个内部类——我总是在它们后面加上“表达式”——它公开了流畅的 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);

【讨论】:

  • 喜欢你最后一个命名参数的例子!
【解决方案2】:

您可以使用任何版本的 .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");

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      查看:

      Guidelines to Fluent Interface design in C# part 1

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

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

      最好的问候,

      安德烈·维安娜

      【讨论】:

        【解决方案5】:

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

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

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

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

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

        【讨论】:

          【解决方案6】:

          想到了一些在 .Net 3.5/C# 3.0 中可能发生的事情:

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

          2. 您也许可以使用对象初始化来模拟 fluent,但这仅适用于实例化时,并且仅适用于单参数方法(其中属性只是一个 setter)。这对我来说似乎很骇人听闻,但确实如此。

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

          【讨论】:

            【解决方案7】:

            这就是我构建所谓的 fluent 接口或我唯一的 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 的原因。正如 Brad 指出的那样,您可以在任何版本的 .net 中执行此操作。尽管我认为 lambda 会带来更多有趣的可能性,例如这样。

            ======

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

            【讨论】:

              【解决方案8】:

              C# 4.0 中的Dynamic 关键字将使编写动态样式构建器成为可能。看看关注article关于 JSON 对象构造的内容。

              【讨论】:

                猜你喜欢
                • 2011-04-13
                • 1970-01-01
                • 2017-06-17
                • 1970-01-01
                • 1970-01-01
                • 2010-10-14
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多