Шаблон проектирования Builder с наследованием: есть ли лучший способ?
Я создаю серию сборщиков, чтобы очистить синтаксис, который создает классы домена для моих макетов, как часть улучшения наших общих модульных тестов. Мои разработчики по существу заполняют класс домена (например, 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()
);
Ответы
Ответ 1
Все, что я могу сказать, заключается в том, что если есть способ сделать это, я тоже хочу знать об этом - я использую именно этот шаблон в Protocol Buffers порт. На самом деле, я рад видеть, что к нему обратился кто-то другой - это значит, что мы, по крайней мере, будем правы!
Ответ 2
Я знаю, что это старый вопрос, но я думаю, что вы можете использовать простой бросок, чтобы избежать 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;
}
}
Ответ 3
Это хорошая стратегия реализации для С#.
Некоторые другие языки (не могут придумать название языка исследования, в котором я это видел) имеют системы типов, которые либо поддерживают ковариантный "я" /"this" напрямую, либо имеют другие умные способы выражения этого шаблона, но с системой типа С# это хорошее (только?) решение.