带继承的生成器设计模式:有更好的方法吗? [英] Builder design pattern with inheritance: is there a better way?

查看:148
本文介绍了带继承的生成器设计模式:有更好的方法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一系列构建器来清理为我的模拟创建域类的语法,作为改进我们的整体单元测试的一部分。我的构建者实质上填充了一个域类(例如 Schedule ),其中一些值通过调用适当的 WithXXX 和链它们在一起。



我在构建器中遇到了一些共同点,我想将它抽象到基类中以增加代码重用。不幸的是,我最终得到的结果如下所示:

  public abstract class BaseBuilder< T,BLDR>其中BLDR:BaseBuilder< T,BLDR> 
其中T:new()
{
public abstract T Build();

protected int Id {get;私人设置; }
受保护的抽象BLDR这{get; }

public BLDR WithId(int id)
{
Id = id;
返回这个;


特别注意受保护的抽象BLDR此{get; }



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

  public class ScheduleIntervalBuilder:
BaseBuilder< ScheduleInterval,ScheduleIntervalBuilder>
{
private int _scheduleId;
// ...

// UG!这里的问题是:
保护覆盖ScheduleIntervalBuilder这
{
get {return this; }
}

public override ScheduleInterval Build()
{
返回新的ScheduleInterval
{
Id = base.Id,
ScheduleId = _scheduleId
// ...
};
}

public Sc​​heduleIntervalBuilder WithScheduleId(int scheduleId)
{
_scheduleId = scheduleId;
返回此;
}

// ...
}

因为BLDR不是BaseBuilder类型,我不能在 WithId(int)方法中使用 return this c $ c> BaseBuilder 。



暴露子类型的属性 abstract BLDR This {get; } 我的唯一选择在这里,或者我缺少一些语法技巧?



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

最终的结果是让构建者构建一个可以以[程序员]可读格式从数据库中检索的概要域类。 ...

  mock.Expect(m => m.Select(It.IsAny< int>( )))。返回(
新计划
{
ScheduleId = 1
// ...
}
);

已经可以读取了。可选的构建器语法是:

  mock.Expect(m => m.Select(It.IsAny< int>() ))。返回(
新的ScheduleBuilder()
.WithId(1)
// ...
.Build()
);

优点我正在寻找使用构建器(并实现所有这些 WithXXX 方法)将抽象出复杂的属性创建(使用正确的 Lookup.KnownValues 自动扩展数据库查找值,而不显示数据库)并且让构建者为领域类提供通常可重用的测试配置文件...

  mock.Expect(m => m.Select ().IsAny< int>()))。 


解决方案

我只能说如果 是这样做的一种方式,我也想知道它 - 我在 Protocol Buffers port 。事实上,我很高兴看到其他人诉诸了它 - 这意味着我们至少有可能是正确的!


I'm creating a series of builders to clean up the syntax which creates domain classes for my mocks as part of improving our overall unit tests. My builders essentially populate a domain class (such as a Schedule) with some values determined by invoking the appropriate WithXXX and chaining them together.

I've encountered some commonality amongst my builders and I want to abstract that away into a base class to increase code reuse. Unfortunately what I end up with looks like:

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;
    }
}

Take special note of the protected abstract BLDR This { get; }.

A sample implementation of a domain class builder is:

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;
    }

    // ...
}

Because BLDR is not of type BaseBuilder I cannot use return this in the WithId(int) method of BaseBuilder.

Is exposing the child type with the property abstract BLDR This { get; } my only option here, or am I missing some syntax trick?

Update (since I can show why I'm doing this a bit more clearly):

The end result is to have builders that build profiled domain classes that one would expect to retrieve from the database in a [programmer] readable format. There's nothing wrong with...

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

as that's pretty readable already. The alternative builder syntax is:

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

the advantage I'm looking for out of using builders (and implementing all these WithXXX methods) is to abstract away complex property creation (automatically expand our database lookup values with the correct Lookup.KnownValues without hitting the database obviously) and having the builder provide commonly reusable test profiles for domain classes...

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

解决方案

All I can say is that if there is a way of doing it, I want to know about it too - I use exactly this pattern in my Protocol Buffers port. In fact, I'm glad to see that someone else has resorted to it - it means we're at least somewhat likely to be right!

这篇关于带继承的生成器设计模式:有更好的方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆