19

全体的な単体テストの改善の一環として、モック用のドメイン クラスを作成する構文をクリーンアップする一連のビルダーを作成しています。私のビルダーは基本的に、ドメイン クラス ( 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()
);
4

3 に答える 3

13

私が言えることは、それを行う方法がある場合、それについても知りたいということです.Protocol Buffersポートでまさにこのパターンを使用しています. 実際、他の誰かがそれに頼っていることを知ってうれしいです。

于 2008-10-28T20:44:32.230 に答える
4

これは古い質問であることは知っていますが、単純なキャストを使用して回避できると思います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;
    }
}
于 2012-02-11T23:27:37.190 に答える
2

これは、C# の優れた実装戦略です。

他のいくつかの言語 (私がこれを見た研究言語の名前が思い出せません) には、共変の「自己」/「これ」を直接サポートする型システム、またはこのパターンを表現する他の巧妙な方法がある型システムがありますが、C# の型システム、これは良い (唯一の?) ソリューションです。

于 2008-10-29T03:30:46.920 に答える