我正在创建一系列构建器来清理为我的模拟创建域类的语法,作为改进我们整体单元测试的一部分。我的构建器本质上填充了一个域类(例如 a Schedule
),其中一些值是通过调用适当的WithXXX
并将它们链接在一起来确定的。
我在构建者中遇到了一些共性,我想将其抽象为基类以增加代码重用。不幸的是,我最终得到的结果如下:
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 类型,所以我不能return this
在WithId(int)
.BaseBuilder
在这里公开带有属性的子类型是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()
);