继承的Builder设计模式:有更好的方法吗?

时间:2008-10-28 20:38:28

标签: c# generics builder

我正在创建一系列构建器来清理语法,为我的模拟创建域类,作为改进整体单元测试的一部分。我的构建器基本上填充了一个域类(例如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

3 个答案:

答案 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#的类型系统,这是一个很好的(只有?)解决方案。