Ich erstelle eine Reihe von Buildern, um die Syntax zu bereinigen, die Domänenklassen für meine Mocks als Teil der Verbesserung unserer gesamten Unit-Tests erstellt. Meine Builder bestücken im Wesentlichen eine Domänenklasse (z. B. eine Schedule
) mit einigen Werten, die durch Aufrufen der entsprechenden WithXXX
und sie miteinander zu verketten.
Ich bin auf einige Gemeinsamkeiten zwischen meinen Buildern gestoßen und möchte diese in eine Basisklasse abstrahieren, um die Wiederverwendung von Code zu erhöhen. Leider sieht das, was ich am Ende habe, so aus:
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;
}
}
Achten Sie besonders auf die protected abstract BLDR This { get; }
.
Eine Beispielimplementierung eines Domain Class Builders 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 BaseBuilder ist, kann ich nicht verwenden return this
im WithId(int)
Methode der BaseBuilder
.
Wird der untergeordnete Typ mit der Eigenschaft abstract BLDR This { get; }
meine einzige Option, oder übersehe ich hier einen Syntaxtrick?
Update (da ich nun etwas deutlicher zeigen kann, warum ich das tue):
Das Endergebnis ist, dass die Builder profilierte Domänenklassen erstellen, von denen man erwarten würde, dass sie in einem für [Programmierer] lesbaren Format aus der Datenbank abgerufen werden. Es ist nichts falsch mit...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new Schedule
{
ScheduleId = 1
// ...
}
);
denn das ist schon ziemlich gut lesbar. Die alternative Syntax des Builders ist:
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.WithId(1)
// ...
.Build()
);
den Vorteil, den ich mir von der Verwendung von Buildern erhoffe (und der Implementierung all dieser WithXXX
Methoden) ist die Abstrahierung der komplexen Eigenschaftserstellung (automatische Erweiterung unserer Datenbanknachschlagewerte mit den richtigen Lookup.KnownValues
ohne auf die Datenbank zuzugreifen) und dass der Builder allgemein wiederverwendbare Testprofile für Domänenklassen bereitstellt...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.AsOneDay()
.Build()
);