54

Вопрос по c# – Метод условного построителя

Мне было интересно, что будет лучшим способом реализации.When состояние вfluent interface с помощьюmethod chaining вBuilder объект?

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

var level = 5;

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

Update - Образец решения можно найтиВот.

  • Error: User Rate Limit ExceededWhen(condition())Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit ExceededWhenError: User Rate Limit ExceededNinjaBuilderError: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • 82

    Что я делаю, так это имею

    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 после первого вызова другого метода. Это имеет дополнительное преимущество / сложность в том, что потенциально допускает и вложенные / составные условия. Хлоп.

  • 6

    Вы можете написать перегруженные версии 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() 
    

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

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

  • 9

    У меня есть решение для цепочки интерфейса

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

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

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

    например

    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 { }
    

  • 6

    Вы можете иметь условный необязательный параметр в вашем методе

    который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)
    )
    

    Это более общее и может быть использовано с любыми методами конструктора.

    Вы даже можете добавить необязательный & quot; еще & quot ;:

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

    Или как& Quot; & Mixin Quot;:

    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 ...
    

    Это, конечно, способ создать & quot; if & quot; операторы как вызовы методов. Другие способы также могут работать:

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

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

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

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

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