2008-10-28 4 views
16

Я создаю серию строителей, чтобы очистить синтаксис, который создает классы домена для моих макетов как часть улучшения наших общих модульных тестов. Мои разработчики по существу заполняют класс домена (например, Schedule) с некоторыми значениями, определяемыми путем вызова соответствующего WithXXX и объединения их вместе.Дизайн шаблона Builder с наследованием: есть ли лучший способ?

Я столкнулся с некоторой общности среди моих строителей, и я хочу отвлечь это в базовом классе, чтобы увеличить повторное использование кода. К сожалению, то, что я в конечном итоге выглядит как:

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; } Мой единственный вариант здесь, или я пропустил какой-то синтаксический трюк?

Update (так как я могу показать, почему я делаю это немного более ясно):

Конечным результатом является то, чтобы строители, которые строят профилированные классы домена, которые можно было бы ожидать, чтобы извлечь из базы данных в [ программист]. Нет ничего плохого в том, что ...

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() 
); 

ответ

11

Все, что я могу сказать, что если это способ сделать это, я хочу знать о это тоже - я использую точно этот узор в моем Protocol Buffers port. На самом деле, я рад видеть, что к нему обратился кто-то другой - это значит, что мы, по крайней мере, скорее всего будем правы!

+0

Yup похоже, что мы застряли с "abstract T Это {get;}" Я соглашусь и закрываю, так как я думаю, что решение - это все, что доступно. – cfeduke 2008-10-29 14:50:11

+0

Я пытаюсь найти относительно четкий способ создания строителей и нашел эту статью (http://www.codeproject.com/Articles/240756/Hierarchical-Implementing-the-Bolchs-Builder-Pat). Он предлагает бросить его один раз (защищенный BLDR _this; _this = (BLDR) this; return _this;), но я не могу понять, почему автор добавил класс WindowBuilder: WindowBuilder {} (Посмотрите полный пример на в нижней части статьи). В этом сообщении я вижу почти тот же шаблон, но cfeduke использует класс ScheduleIntervalBuilder: BaseBuilder ... – 2012-06-22 21:59:49

2

Это хорошая стратегия реализации для C#.

Некоторые другие языки (не могут думать о названии языка исследования, в котором я это видел) имеют системы типов, которые либо поддерживают ковариантное «я»/«это» напрямую, либо имеют другие умные способы выражения этого шаблона , но с системой типа C# это хорошее (только?) решение.

3

Я знаю, что это старый вопрос, но я думаю, что вы можете использовать простой бросок, чтобы избежать 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; 
    } 
} 
Смежные вопросы