Гибкий интерфейс для создания цепочек методов условного построителя

Мне было интересно, как лучше всего реализовать условие .When в свободном интерфейсе с использованием цепочки методов в объекте Builder?

Например, как мне реализовать методы .WithSkill() и .When() в следующем примере:

var level = 5;

var ninja = NinjaBuilder
    .CreateNinja()
    .Named("Ninja Boy")
    .AtLevel(level)
    .WithShurikens(10)
    .WithSkill(Skill.HideInShadows)
        .When(level > 3)
    .Build()

Обновление - образец решения можно найти здесь < / а>.


person Ken Burkhardt    schedule 06.04.2012    source источник


Ответы (4)


Что я бы сделал, так это NinjaBuilder сохранить операции как список делегатов, а не применять их, и применять их только при вызове .Build. Это позволит вам сделать их условными:

public class NinjaBuilder { 
    List<Action<Ninja>> builderActions = new List<Action<Ninja>>();

    public Ninja Build() {
        var ninja = new Ninja();
        builderActions.ForEach(ba => ba(ninja));
        return ninja;
    }

    public NinjaBuilder WithShurikens(int numShirukens) {
        builderActions.Add(n=>n.Shirukens = numShirukens);
        return this;
    }

    public NinjaBuilder When(Boolean condition) {
        if (!condition) // If the condition is not met, remove the last action
            builderActions.RemoveAt(builderActions.Length - 1);
        return this;
    }
}

Конечно, это предполагает, что состояние остается постоянным на момент создания строителя. Если вы хотите сделать его непостоянным, вы можете сделать что-то вроде этого:

    public NinjaBuilder When(Func<Boolean> condition) {
        var oldAction = builderActions[builderActions.Length - 1];
        builderActions[builderActions.Length - 1] = n => { if (condition()) { oldAction(n); } }
        return this;
    }

Если вы хотите, чтобы компилятор несколько раз проверял When, вы можете сделать builderActions защищенным и сделать что-то вроде этого:

public class ConditionalNinjaBuilder : NinjaBuilder {
    public ConditionalNinjaBuilder(NinjaBuilder wrappedBuilder) {            
        // Since someone might call .WithShirukens on the wrapping
        // builder directly, we should make sure that our actions 
        // list is the same instance as the one in our wrapped builder
        builderActions = wrappedBuilder.builderActions;
    }

    public ConditionalNinjaBuilder When(Func<Boolean> condition) {
        var oldAction = builderActions[builderActions.Length - 1];
        builderActions[builderActions.Length - 1] = n => { if (condition()) { oldAction(n); } }
        return this;
    }
}

и исходные операции возвращают ConditionalNinjaBuilder:

    public ConditionalNinjaBuilder WithShurikens(int numShirukens) {
        builderActions.Add(n=>n.Shirukens = numShirukens);
        return new ConditionalNinjaBuilder(this);
    }

Таким образом, вы можете вызвать .When только после первого вызова другого метода. Это имеет дополнительное преимущество / сложность, так как потенциально позволяет использовать вложенные / составные условные выражения. Ой.

person Chris Shain    schedule 06.04.2012
comment
Обратите внимание, что теперь вы делаете явное предположение о том, когда вы можете использовать When - только сразу после вашего условия. Вы не применяете это принудительно, поэтому может быть подвержено ошибкам. Также ваши текущие сигнатуры методов неверны - каждый должен возвращать NinjaBuilder, иначе это не плавный интерфейс. - person BrokenGlass; 06.04.2012
comment
Исправлен метод sigs. Я постараюсь сделать это явным. - person Chris Shain; 06.04.2012
comment
Дайте мне знать, как это происходит - в прошлом я использовал разные интерфейсы (и соответствующие методы расширения) для обеспечения порядка, я был бы рад увидеть что-то более элегантное - person BrokenGlass; 06.04.2012
comment
@BrokenGlass, посмотрите, что я добавил - вероятно, очень похоже на то, что вы делали раньше. - person Chris Shain; 06.04.2012
comment
@ChrisShain Я не совсем понимаю ваши коды, чтобы сделать его непостоянным. Разве это не то же самое, что When(condition())? По крайней мере, в этом лямбда-уравнении есть синтаксическая ошибка. - person joe; 24.10.2018
comment
@joe разница теперь в том, что для непостоянного метода условное выражение оценивается во время .Build (), а не во время создания самого построителя. В некоторых сценариях вы можете захотеть создать построитель и спрятать его где-нибудь для дальнейшего использования, и возможно, что условие может измениться между временем создания построителя и вызовом сборки. Я не уверен, о какой синтаксической ошибке вы имеете в виду. - person Chris Shain; 24.10.2018
comment
@ChrisShain builderActions[builderActions.Length - 1] = n => condition() ? oldAction(n) : n; Вот что меня смутило :). n - это Ninja экземпляр, а oldAction(n) возвращает значение void. Это не пройдет C # 7. - person joe; 24.10.2018

У меня есть решение для связывания интерфейсов; единственная проблема с моим решением заключается в том, что его сложность (масштаб) растет с каждым новым методом, который вы хотите поддерживать. Но это действительно отличный API для пользователя.

Допустим, у вас есть 3 метода, A, B и C, и вы хотите использовать их в цепочке.

Давайте также учтем, что вы не хотите иметь возможность вызывать какой-либо метод более одного раза.

e.g.

new Builder().A().B().C(); // OK
new Builder().A().B().A(); // Not OK

Этого можно достичь с помощью серьезного умения:

public class Builder : A<Not_A>, B<Not_B>, C<Not_C>, Not_A, Not_B, Not_C, Not_AB, Not_BC, Not_AC, Empty
{
  Not_AB A<Not_AB>.A() { return (Not_AB)A(); }
  Not_AC A<Not_AC>.A() { return (Not_AC)A(); }
  Empty A<Empty>.A() { return (Empty)A(); }
  public Not_A A()
  {
    return (Not_A)this;
  }

  Not_AB B<Not_AB>.B() { return (Not_AB)B(); }
  Not_BC B<Not_BC>.B() { return (Not_BC)B(); }
  Empty B<Empty>.B() { return (Empty)B(); }
  public Not_B B()
  {
    return (Not_B)this;
  }

  Not_AC C<Not_AC>.C() { return (Not_AC)C(); }
  Not_BC C<Not_BC>.C() { return (Not_BC)C(); }
  Empty C<Empty>.C() { return (Empty)C(); }
  public Not_C C()
  {
    return (Not_C)this;
  }
}

public interface Empty { }

public interface A<TRemainder> { TRemainder A(); }
public interface B<TRemainder> { TRemainder B(); }
public interface C<TRemainder> { TRemainder C(); }

public interface Not_A : B<Not_AB>, C<Not_AC> { }
public interface Not_B : A<Not_AB>, C<Not_BC> { }
public interface Not_C : A<Not_AC>, B<Not_BC> { }

public interface Not_AB : C<Empty> { }
public interface Not_BC : A<Empty> { }
public interface Not_AC : B<Empty> { }

А затем смешайте это с потрясающими способностями Криса Шейна, чтобы использовать набор действий!

Решил реализовать. Обратите внимание, что теперь вы не можете вызывать какой-либо метод дважды с этим решением для цепочки. Я поставил ваш When метод как метод расширения.

Вот код вызова:

  int level = 5;
  var ninja = NinjaBuilder
      .CreateNinja()
      .Named("Ninja Boy")
      .AtLevel(level)
      .WithShurikens(10)
      .WithSkill(Skill.HideInShadows)
          .When(n => n.Level > 3)
      .Build();

Вот мои классы ниндзя и навыков:

public class Ninja
{
  public string Name { get; set; }
  public int Level { get; set; }
  public int Shurikens { get; set; }
  public Skill Skill { get; set; }
}

public enum Skill
{
  None = 1,
  HideInShadows
}

Это класс NinjaBuilder:

public class NinjaBuilder : NinjaBuilder_Sans_Named
{
  public static NinjaBuilder CreateNinja() { return new NinjaBuilder(); }
  public Stack<Action<Ninja>> _buildActions;

  public NinjaBuilder()
  {
    _buildActions = new Stack<Action<Ninja>>();
  }

  public override Ninja Build()
  {
    var ninja = new Ninja();
    while (_buildActions.Count > 0)
    {
      _buildActions.Pop()(ninja);
    }

    return ninja;
  }

  public override void AddCondition(Func<Ninja, bool> condition)
  {
    if (_buildActions.Count == 0)
      return;

    var top = _buildActions.Pop();
    _buildActions.Push(n => { if (condition(n)) { top(n); } });
  }

  public override Sans_Named_NinjaBuilder Named(string name)
  {
    _buildActions.Push(n => n.Name = name);
    return this;
  }

  public override Sans_AtLevel_NinjaBuilder AtLevel(int level)
  {
    _buildActions.Push(n => n.Level = level);
    return this;
  }

  public override Sans_WithShurikens_NinjaBuilder WithShurikens(int shurikenCount)
  {
    _buildActions.Push(n => n.Shurikens = shurikenCount);
    return this;
  }

  public override Sans_WithSkill_NinjaBuilder WithSkill(Skill skillType)
  {
    _buildActions.Push(n => n.Skill = skillType);
    return this;
  }
}

А остальная часть этого кода - просто накладные расходы, чтобы заставить работать преобразования и вызовы:

public abstract class NinjaBuilderBase :
  EmptyNinjaBuilder,
  Named_NinjaBuilder<Sans_Named_NinjaBuilder>,
  AtLevel_NinjaBuilder<Sans_AtLevel_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_WithSkill_NinjaBuilder>
{
  public abstract void AddCondition(Func<Ninja, bool> condition);
  public abstract Ninja Build();

  public abstract Sans_WithSkill_NinjaBuilder WithSkill(Skill skillType);
  public abstract Sans_WithShurikens_NinjaBuilder WithShurikens(int shurikenCount);
  public abstract Sans_AtLevel_NinjaBuilder AtLevel(int level);
  public abstract Sans_Named_NinjaBuilder Named(string name);
}

public abstract class NinjaBuilder_Sans_WithSkill : NinjaBuilderBase,
  Sans_WithSkill_NinjaBuilder
{
  Sans_Named_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithSkill_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithSkill_NinjaBuilder)AtLevel(level); }
  Sans_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
}

public abstract class NinjaBuilder_Sans_WithShurikens : NinjaBuilder_Sans_WithSkill,
  Sans_WithShurikens_NinjaBuilder,
  Sans_WithShurikens_WithSkill_NinjaBuilder
{
  Sans_Named_WithShurikens_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)AtLevel(level); }
  Sans_Named_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
  Sans_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
}

public abstract class NinjaBuilder_Sans_AtLevel : NinjaBuilder_Sans_WithShurikens,
  Sans_AtLevel_NinjaBuilder,
  Sans_AtLevel_WithShurikens_NinjaBuilder,
  Sans_AtLevel_WithSkill_NinjaBuilder,
  Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder
{
  EmptyNinjaBuilder Named_NinjaBuilder<EmptyNinjaBuilder>.Named(string name) { return Named(name); }
  Sans_Named_AtLevel_WithSkill_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
  Sans_Named_AtLevel_WithShurikens_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
  Sans_Named_AtLevel_NinjaBuilder Named_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>.Named(string name) { return (Sans_Named_AtLevel_NinjaBuilder)Named(name); }
  Sans_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
  Sans_AtLevel_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_AtLevel_WithSkill_NinjaBuilder)WithSkill(skillType); }
}

public abstract class NinjaBuilder_Sans_Named : NinjaBuilder_Sans_AtLevel,
  Sans_Named_NinjaBuilder,
  Sans_Named_AtLevel_NinjaBuilder,
  Sans_Named_WithShurikens_NinjaBuilder,
  Sans_Named_WithSkill_NinjaBuilder,
  Sans_Named_WithShurikens_WithSkill_NinjaBuilder,
  Sans_Named_AtLevel_WithSkill_NinjaBuilder,
  Sans_Named_AtLevel_WithShurikens_NinjaBuilder
{
  EmptyNinjaBuilder WithSkill_NinjaBuilder<EmptyNinjaBuilder>.WithSkill(Skill skillType) { return (EmptyNinjaBuilder)WithSkill(skillType); }
  EmptyNinjaBuilder WithShurikens_NinjaBuilder<EmptyNinjaBuilder>.WithShurikens(int shurikenCount) { return (EmptyNinjaBuilder)WithShurikens(shurikenCount); }
  EmptyNinjaBuilder AtLevel_NinjaBuilder<EmptyNinjaBuilder>.AtLevel(int level) { return (EmptyNinjaBuilder)AtLevel(level); }
  Sans_Named_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.AtLevel(int level) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
  Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
  Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
  Sans_Named_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
  Sans_Named_AtLevel_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)WithSkill(skillType); }
  Sans_Named_AtLevel_NinjaBuilder AtLevel_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>.AtLevel(int level) { return (Sans_Named_AtLevel_NinjaBuilder)AtLevel(level); }
  Sans_Named_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_NinjaBuilder>.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
  Sans_Named_WithSkill_NinjaBuilder WithSkill_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>.WithSkill(Skill skillType) { return (Sans_Named_WithSkill_NinjaBuilder)WithSkill(skillType); }
}

public static class NinjaBuilderExtension
{
  public static TBuilderLevel When<TBuilderLevel>(this TBuilderLevel ths, Func<Ninja, bool> condition) where TBuilderLevel : EmptyNinjaBuilder
  {
    ths.AddCondition(condition);
    return ths;
  }
}

public interface EmptyNinjaBuilder { void AddCondition(Func<Ninja, bool> condition); Ninja Build(); }

public interface Named_NinjaBuilder<TRemainder> { TRemainder Named(string name); }
public interface AtLevel_NinjaBuilder<TRemainder> { TRemainder AtLevel(int level);}
public interface WithShurikens_NinjaBuilder<TRemainder> { TRemainder WithShurikens(int shurikenCount); }
public interface WithSkill_NinjaBuilder<TRemainder> { TRemainder WithSkill(Skill skillType); }

// level one reductions
public interface Sans_Named_NinjaBuilder :
  AtLevel_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_AtLevel_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_AtLevel_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_WithShurikens_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
  AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_WithSkill_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_WithSkill_NinjaBuilder>,
  AtLevel_NinjaBuilder<Sans_AtLevel_WithSkill_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }

// level two reductions
// Named
public interface Sans_Named_AtLevel_NinjaBuilder :
  WithShurikens_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_Named_WithShurikens_NinjaBuilder :
  AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_Named_WithSkill_NinjaBuilder :
  AtLevel_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
// AtLevel
public interface Sans_AtLevel_WithShurikens_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_AtLevel_WithShurikens_NinjaBuilder>,
  WithSkill_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
public interface Sans_AtLevel_WithSkill_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_AtLevel_WithSkill_NinjaBuilder>,
  WithShurikens_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }
// WithShurikens
public interface Sans_WithShurikens_WithSkill_NinjaBuilder :
  Named_NinjaBuilder<Sans_Named_WithShurikens_WithSkill_NinjaBuilder>,
  AtLevel_NinjaBuilder<Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder>,
  EmptyNinjaBuilder { }

// level three reductions
// Named
public interface Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder :
  Named_NinjaBuilder<EmptyNinjaBuilder>,
  EmptyNinjaBuilder { }
// AtLevel
public interface Sans_Named_WithShurikens_WithSkill_NinjaBuilder :
  AtLevel_NinjaBuilder<EmptyNinjaBuilder>,
  EmptyNinjaBuilder { }
// WithShurikens
public interface Sans_Named_AtLevel_WithSkill_NinjaBuilder :
  WithShurikens_NinjaBuilder<EmptyNinjaBuilder>,
  EmptyNinjaBuilder { }
// WithSkill
public interface Sans_Named_AtLevel_WithShurikens_NinjaBuilder :
  WithSkill_NinjaBuilder<EmptyNinjaBuilder>,
  EmptyNinjaBuilder { }
person payo    schedule 06.04.2012
comment
Это здорово, особенно в сочетании с тем, что было написано для jOOQ на blog.jooq.org/2012/01/05/, в котором показано, как реализовать любой обычный язык с помощью интерфейсов. Обратите внимание, что вам не нужен этот взрыв интерфейса, вам просто нужен один интерфейс для каждого перехода в конечном автомате, представленном вашим обычным языком. - person Arthur van Leeuwen; 02.05.2014

В вашем методе может быть условный необязательный параметр, который по умолчанию равен true:

.WithSkill(Skill.HideInShadows, when: level > 3)

Конечно, это будет очень специфично для метода WithSkill:

public NinjaBuilder WithSkill(Skill skill, bool when = true) {
  if (!when) return this;
  // ...
}

Вы можете добавить его к другим методам, которые тоже должны быть условными.

Другой вариант - иметь метод, который вкладывает условные части построителя:

public NinjaBuilder When(bool condition, Action<NinjaBuilder> then) {
  if (condition) then(this);
  return this;
}

Тогда вы можете написать это так:

.When(level > 3, 
  then: _ => _.WithSkill(Skill.HideInShadows))

Или вот так:

.When(level > 3, _=>_
  .WithSkill(Skill.HideInShadows)
)

Это более общий вариант, и его можно использовать с любыми методами построителя.

Вы даже можете добавить необязательное "else":

public NinjaBuilder When(bool condition, Action<NinjaBuilder> then, Action<NinjaBuilder> otherwise = null) {
  if (condition) {
    then(this);
  }
  else if (otherwise != null) {
    otherwise(this);
  }
  return this;
}

Или как "миксин":

public interface MBuilder {}
public static class BuilderExtensions {
  public static TBuilder When<TBuilder>(this TBuilder self, bool condition, Action<TBuilder> then, Action<TBuilder> otherwise = null)
  where TBuilder : MBuilder
  {
    if (condition) {
      then(self);
    }
    else if (otherwise != null) {
      otherwise(self);
    }
    return self;
  }
}

public class NinjaBuilder : MBuilder ...

Это, конечно, способ создания операторов «если» в виде вызовов методов. Другие способы также могут работать:

.When(level > 3) // enter "conditional" context
  .WithSkill(Skill.HideInShadows)
.End() // exit "conditional" context

В этом случае построитель отслеживает, должен ли он игнорировать любые вызовы методов, которые выполняются в «условном» контексте, если условие ложно. When войдет в контекст, End выйдет из него. У вас также может быть вызов Otherwise(), чтобы отметить контекст «else». Интересно, что вы также можете охватить другие подобные утверждения, например циклы:

.Do(times: 10) // add 10 shurikens
  .AddShuriken()
.End()

В этом случае вызовы, сделанные в контексте "цикла", должны быть записаны и воспроизведены желаемое количество раз при вызове End.

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

person Community    schedule 06.04.2012

Вы можете подумать о написании перегруженных версий With, а во втором - взять в качестве аргумента Where:

var level = 5;  
var ninja = NinjaBuilder     
     .CreateNinja()
     .Named("Ninja Boy")
     .AtLevel(level)
     .WithShurikens(10)
     .WithSkill(Skill.HideInShadows, Where.Level(l => l > 3))
     .Build() 

Конечно, это основано на том, что вы собираетесь писать Where как отдельный объект, который по сути выглядит так:

public sealed static class Where
{
    public bool Defense (Func<int, bool> predicate) { return predicate(); }
    public bool Dodge (Func<int, bool> predicate) { return predicate(); }
    public bool Level (Func<int, bool> predicate) { return predicate(); }

}
person Mike Hofer    schedule 06.04.2012