具有inheritance的Builder设计模式:有更好的方法吗?

我正在创建一系列构建器来清理语法,为我的模拟创建域类,作为改进整体unit testing的一部分。 我的构建器基本上填充了一个域类(例如一个Schedule ),其中一些值通过调用适当的WithXXX并将它们链接在一起来确定。

我在构建器中遇到了一些共性,我想将它抽象到基类中以增加代码重用。 不幸的是,我最终看起来像:

 public abstract class BaseBuilder where BLDR : BaseBuilder where T : new() { public abstract T Build(); protected int Id { get; private set; } protected abstract BLDR This { get; } public BLDR WithId(int id) { Id = id; return This; } } 

特别注意protected abstract BLDR This { get; } protected abstract BLDR This { get; }

域类构建器的示例实现是:

 public class ScheduleIntervalBuilder : BaseBuilder { private int _scheduleId; // ... // UG! here's the problem: protected override ScheduleIntervalBuilder This { get { return this; } } public override ScheduleInterval Build() { return new ScheduleInterval { Id = base.Id, ScheduleId = _scheduleId // ... }; } public ScheduleIntervalBuilder WithScheduleId(int scheduleId) { _scheduleId = scheduleId; return this; } // ... } 

因为BLDR不是BaseBuilder类型,所以我不能在WithId(int)方法中使用BaseBuilder

暴露子类型与属性abstract BLDR This { get; } 我这里唯一的选择,还是我错过了一些语法技巧?

更新(因为我可以说明为什么我这样做更清楚):

最终结果是让构建器构建一个人们期望以[程序员]可读格式从数据库中检索的分析域类。 ……没有错

 mock.Expect(m => m.Select(It.IsAny())).Returns( new Schedule { ScheduleId = 1 // ... } ); 

因为那已经很可读了。 替代构建器语法是:

 mock.Expect(m => m.Select(It.IsAny())).Returns( new ScheduleBuilder() .WithId(1) // ... .Build() ); 

我正在寻找使用构建器(以及实现所有这些WithXXX方法)的优势是抽象出复杂的属性创建(使用正确的Lookup.KnownValues自动扩展我们的数据库查找值,而不会明显地访问数据库)并让构建器提供通常可重用的域类测试配置文件…

 mock.Expect(m => m.Select(It.IsAny())).Returns( new ScheduleBuilder() .AsOneDay() .Build() ); 

我只能说,如果有办法,我也想了解它 – 我在Protocol Buffers端口中使用这种模式。 事实上,我很高兴看到其他人已经诉诸它 – 这意味着我们至少有可能是正确的!

我知道这是一个老问题,但我认为你可以使用一个简单的abstract BLDR This { get; }来避免abstract BLDR This { get; } abstract BLDR This { get; }

结果代码将是:

 public abstract class BaseBuilder where BLDR : BaseBuilder where T : new() { public abstract T Build(); protected int Id { get; private set; } public BLDR WithId(int id) { _id = id; return (BLDR)this; } } public class ScheduleIntervalBuilder : BaseBuilder { private int _scheduleId; // ... public override ScheduleInterval Build() { return new ScheduleInterval { Id = base.Id, ScheduleId = _scheduleId // ... }; } public ScheduleIntervalBuilder WithScheduleId(int scheduleId) { _scheduleId = scheduleId; return this; } // ... } 

当然你可以封装构建器

 protected BLDR This { get { return (BLDR)this; } } 

这是C#的一个很好的实现策略。

其他一些语言(想不到我见过的研究语言的名称)有类型系统可以直接支持协变的“自我”/“这个”,或者有其他聪明的方式来表达这种模式,但是用C#的类型系统,这是一个很好的(只有?)解决方案。

    Interesting Posts