Я создаю серию сборщиков, чтобы очистить синтаксис, который создает классы домена для моих макетов, как часть улучшения наших общих модульных тестов. Мои разработчики по существу заполняют класс домена (например, 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 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()
);