Frage

Ich bin eine Reihe von Bauherren zu schaffen, die Syntax, um aufzuräumen, die unser Gesamteinheit zu verbessern, Tests Domain-Klassen für meine Mocks im Rahmen schafft. Meine Bauherren bevölkern im Wesentlichen einen Domain-Klasse (wie ein Schedule) mit einigen bestimmten Werten durch die entsprechende WithXXX aufrufen und sie zusammen verketten.

Ich habe einige Gemeinsamkeiten unter meinen Bauherren begegnet, und ich möchte zu abstrahieren, die in einer Basisklasse entfernt Wiederverwendung von Code zu erhöhen. Leider, was ich am Ende mit sieht aus wie:

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

Beachten Sie besonders die protected abstract BLDR This { get; }.

Eine Beispielimplementierung einer Domain-Klasse Builder ist:

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

    // ...
}

Da BLDR nicht vom Typ ist BaseBuilder ich nicht return this im WithId(int) Methode von BaseBuilder verwenden kann.

aussetzt das Kind Typ mit der Eigenschaft abstract BLDR This { get; } meine einzige Option hier, oder bin ich etwas Syntax Trick fehlt?

Update (da kann ich zeigen, warum ich dies ein wenig deutlicher mache):

Das Endergebnis ist, Bauherren zu haben, die Domain-Klassen Profil bauen, die man erwarten würde, aus der Datenbank in einem [Programmierer] lesbaren Format abzurufen. Es ist nichts falsch mit ...

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

als das ziemlich lesbar ist schon. Die alternative Builder Syntax:

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

Der Vorteil Ich suche aus Erbauer der Verwendung (und all diese WithXXX Methoden der Umsetzung) ist zu abstrahieren komplexer Eigenschaft Erstellung (automatisch unsere Datenbank-Lookup-Werte mit dem korrekten Lookup.KnownValues erweitern, ohne die Datenbank offensichtlich zu schlagen) und den Generator mit bieten häufig wiederverwendbare Testprofile für Domain-Klassen ...

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

Lösung

Alles, was ich sagen kann, ist, dass, wenn es ist eine Art und Weise, es zu tun, ich will auch darüber Bescheid wissen - ich verwende genau dieses Muster in meinem Protocol Buffers Port . In der Tat, ich bin froh, anderes zu sehen, dass jemand auf sie ausgeübt hat - es bedeutet, können wir zumindest etwas wahrscheinlich Recht sein

Andere Tipps

Ich weiß, dass dies eine alte Frage, aber ich denke, Sie eine einfache Guss kann die abstract BLDR This { get; }

vermeiden

Der resultierende Code wäre dann:

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

    // ...
}

Natürlich können Sie den Erbauer kapseln mit

protected BLDR This
{
    get
    {
        return (BLDR)this;
    }
}

Dies ist eine gute Umsetzungsstrategie für C #.

Einige andere Sprachen (kann nicht glauben, der Name der Forschung Sprache habe ich diese in gesehen) haben Typ-Systeme, die entweder einen kovarianten „Selbst“ unterstützen / „das“ direkt oder andere clevere Möglichkeiten haben diese Muster zum Ausdruck bringen, aber mit C # 's-Typ-System, das ist eine gute (nur?) Lösung.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top