我正在创建一系列的构建器来清理语法,以创建领域类作为改进我们整体单元测试的一部分。我的构建器本质上是使用适当的WithXXX
调用并将它们链接在一起来填充领域类(如Schedule
)的某些值。
我在我的构建器中遇到了一些共性,想要将其抽象出来成为一个基类,以增加代码重用性。不幸的是,最终看起来像:
public abstract class BaseBuilder<T,BLDR> where BLDR : BaseBuilder<T,BLDR>
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; }
。
领域类构建器的示例实现如下:
public class ScheduleIntervalBuilder :
BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder>
{
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类型,所以我不能在BaseBuilder的WithId(int)方法中使用return this。在此处使用属性abstract BLDR This { get; }来公开子类型是否是我的唯一选择,或者我错过了某些语法技巧?
更新(因为我可以更清楚地展示我为什么要这样做):
最终结果是拥有构建器来构建受配置文件支持的域类,人们期望以[程序员]可读格式从数据库中检索。 这没有任何问题...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new Schedule
{
ScheduleId = 1
// ...
}
);
因为这已经相当易读。另一种构建器语法是:
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.WithId(1)
// ...
.Build()
);
我希望使用构建器(并实现所有这些WithXXX
方法)的优点是抽象出复杂的属性创建过程(自动扩展我们的数据库查找值与正确的Lookup.KnownValues
匹配,而不会实际访问数据库),并且构建器为领域类提供常用的可重用测试配置文件...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.AsOneDay()
.Build()
);