【问题标题】:Designing fluent builders to create an object graph in C#设计流畅的构建器以在 C# 中创建对象图
【发布时间】:2011-11-09 22:12:48
【问题描述】:

我第一次尝试用 C# 编写流畅的构建器,这样我就可以简化创建对象图的逻辑。

我的第一个想法是为每个类创建一个流利的构建器,然后像这样分层嵌套它们:

School school = SchoolBuilder.New()
  .WithName("Whatchamatta U")
  .AddClass(
      ClassBuilder.New()
      .WithName("Arithmetic")
      .WithClassNumber("101")
      .AddStudent(
         StudentBuilder.New()
         .WithName("John Smith")
      )
      .AddStudent(
         StudentBuilder.New()
         .WithName("Jane Smith")
      )
  )
  .Save()

这很容易实现为使用 C# 接口的状态机,并且相当容易理解,无论是作为尝试阅读代码的开发人员还是作为尝试创建新代码的开发人员(“With*”=设置属性, "Add*"=添加一个子对象,"Save" 做任何需要做的事情来创建图形),但我认为开发人员可能更容易读取和编写如下所示的 DSL:

School school = SchoolBuilder.New()
  .WithName("Whatchamatta U")
  .AddClass()
    .WithClassName("Arithmetic")
    .WithClassNumber("101")
    .AddStudent()
       .WithStudentName("John Smith")
    .AddStudent()
       .WithStudentName("Jane Smith")         
  .Save()

由于所有构建器的消息都通过最顶层的父构建器对象,我想我需要找到一些方法将它们沿着层次结构路由到适当的子构建器对象。在 Ruby 中,这可以通过 method_missing 来完成,但我看不到在 C# 中实现它的聪明方法。

一个丑陋的解决方案是让每个父对象实现每个潜在后代对象的接口,其中每个方法将其调用路由到子对象。显然这是不可取的,特别是当我向层次结构中添加更多构建器类时,但我不知道在编译时“虚拟”实现这些接口但拦截对该接口的所有调用并将它们代理到树下的简洁方法, à la method_missing。我可以看到有一些方法可以在 C# 中使用动态代理在运行时动态实现接口,但我认为这些方法都不支持智能感知。

我的要求是 a) 这适用于智能感知和 b) 任何构建器都可以用于构建对象而不参考其父对象——这意味着我希望所有构建器都能够处理其子构建器的构建 (这意味着丑陋的解决方案将是真的丑陋)

有没有办法在 C# 中采用第二条路线?或者这是错误的思考方式?

【问题讨论】:

  • 在花了一些时间之后,我认为 #2 不是一个好主意,尽管它很干净。我使用 C# 伪混合思想实现了一个“主控制器”——每个类都有自己的构建器混合——认为无论我从哪个级别开始,我都可以通过该级别路由所有命令(即我可以开始在 ClassBuilder.new())。这有效,但仅限于创造性地使用泛型,例如.AddClass(),这使得语法非常难看。另一个缺点是您会遇到各种名称冲突。我有 #1 的工作,而且要简单得多。

标签: c# interface fluent


【解决方案1】:

替代方法可能是使用object initializer 语法。所以像:

School school = new School {
    Name = "Watchamatta U",
    Classes = new[] {
        new Class {
            Name = "Arithmetic",
            Number = "101",
            Students = new[] {
                // etc.
            }
        }
    }
};

这会初始化属性,因此您可以将数组更改为对象。

【讨论】:

  • 我通过添加 new[](它推断数组类型)并放弃默认构造函数括号(在使用对象初始化器时在 C# 中是可选的)为您清理了它。
  • 谢谢,这是一个很好的选择——对象初始化器和新的 4.0 参数默认值肯定会让这变得非常干净。但我仍在努力弄清楚像这样的流畅接口在 C# 中是否可行。
猜你喜欢
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 2018-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多