我正在创建一系列构建器来清理语法,为我的模拟创建域类,作为改进整体单元测试的一部分。我的构建器基本上填充了一个域类(例如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
的{{1}}方法中使用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
答案 0 :(得分:11)
我只能说,如果是这样做的方式,我也想知道它 - 我在{{3}中使用完全这种模式}}。事实上,我很高兴看到其他人已经诉诸它 - 这意味着我们至少有可能是正确的!
答案 1 :(得分:3)
我知道这是一个老问题,但我认为你可以使用简单的演员来避免abstract BLDR This { get; }
结果代码将是:
public abstract class BaseBuilder<T, BLDR> where BLDR : BaseBuilder<T, BLDR>
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<ScheduleInterval,ScheduleIntervalBuilder>
{
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;
}
}
答案 2 :(得分:2)
这是C#的一个很好的实施策略。
其他一些语言(想不到我见过的研究语言的名称)有类型系统可以直接支持协变“self”/“this”,或者有其他聪明的方式来表达这种模式,但是使用C#的类型系统,这是一个很好的(只有?)解决方案。