Conditional Builder Method Chaining Fluent Interface

前端 未结 4 1555
清歌不尽
清歌不尽 2020-12-07 07:55

I was wondering what would be the best way to implement a .When condition in a fluent interface using method chaining in a

4条回答
  •  佛祖请我去吃肉
    2020-12-07 08:30

    I have a solution to interface chaining; the only problem with my solution is that it grows in complexity (scale) with every new method you want to support. But, it makes for a really awesome API for the user.

    Let us consider that you have 3 methods, A, B, and C, and you want to use them in a chain.

    Let us also consider that you don't want to be able to call any method more than once.

    e.g.

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

    This can be accomplished with some serious awesomeness:

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

    And then, mix this with Chris Shain's awesomeness to use a stack of actions!

    I decided to implement it. Note that you can't call any method twice now with this chaining solution. I put your When method as an extension method.

    Here is the calling code:

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

    Here is my Ninja and Skill classes:

    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
    }
    

    This is the NinjaBuilder class:

    public class NinjaBuilder : NinjaBuilder_Sans_Named
    {
      public static NinjaBuilder CreateNinja() { return new NinjaBuilder(); }
      public Stack> _buildActions;
    
      public NinjaBuilder()
      {
        _buildActions = new Stack>();
      }
    
      public override Ninja Build()
      {
        var ninja = new Ninja();
        while (_buildActions.Count > 0)
        {
          _buildActions.Pop()(ninja);
        }
    
        return ninja;
      }
    
      public override void AddCondition(Func 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;
      }
    }
    

    And the rest of this code is just overhead to make the conversions and calls work:

    public abstract class NinjaBuilderBase :
      EmptyNinjaBuilder,
      Named_NinjaBuilder,
      AtLevel_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      WithSkill_NinjaBuilder
    {
      public abstract void AddCondition(Func 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.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithSkill_NinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (Sans_AtLevel_WithSkill_NinjaBuilder)AtLevel(level); }
      Sans_WithShurikens_WithSkill_NinjaBuilder WithShurikens_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.Named(string name) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)AtLevel(level); }
      Sans_Named_WithSkill_NinjaBuilder Named_NinjaBuilder.Named(string name) { return (Sans_Named_WithSkill_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
      Sans_WithShurikens_WithSkill_NinjaBuilder 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.Named(string name) { return Named(name); }
      Sans_Named_AtLevel_WithSkill_NinjaBuilder Named_NinjaBuilder.Named(string name) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
      Sans_Named_AtLevel_WithShurikens_NinjaBuilder Named_NinjaBuilder.Named(string name) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder.WithSkill(Skill skillType) { return (Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
      Sans_Named_AtLevel_NinjaBuilder Named_NinjaBuilder.Named(string name) { return (Sans_Named_AtLevel_NinjaBuilder)Named(name); }
      Sans_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (Sans_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
      Sans_AtLevel_WithSkill_NinjaBuilder 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.WithSkill(Skill skillType) { return (EmptyNinjaBuilder)WithSkill(skillType); }
      EmptyNinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (EmptyNinjaBuilder)WithShurikens(shurikenCount); }
      EmptyNinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (EmptyNinjaBuilder)AtLevel(level); }
      Sans_Named_AtLevel_WithShurikens_NinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)AtLevel(level); }
      Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithShurikens(shurikenCount); }
      Sans_Named_WithShurikens_WithSkill_NinjaBuilder WithSkill_NinjaBuilder.WithSkill(Skill skillType) { return (Sans_Named_WithShurikens_WithSkill_NinjaBuilder)WithSkill(skillType); }
      Sans_Named_AtLevel_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (Sans_Named_AtLevel_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
      Sans_Named_AtLevel_WithSkill_NinjaBuilder WithSkill_NinjaBuilder.WithSkill(Skill skillType) { return (Sans_Named_AtLevel_WithSkill_NinjaBuilder)WithSkill(skillType); }
      Sans_Named_AtLevel_NinjaBuilder AtLevel_NinjaBuilder.AtLevel(int level) { return (Sans_Named_AtLevel_NinjaBuilder)AtLevel(level); }
      Sans_Named_WithShurikens_NinjaBuilder WithShurikens_NinjaBuilder.WithShurikens(int shurikenCount) { return (Sans_Named_WithShurikens_NinjaBuilder)WithShurikens(shurikenCount); }
      Sans_Named_WithSkill_NinjaBuilder WithSkill_NinjaBuilder.WithSkill(Skill skillType) { return (Sans_Named_WithSkill_NinjaBuilder)WithSkill(skillType); }
    }
    
    public static class NinjaBuilderExtension
    {
      public static TBuilderLevel When(this TBuilderLevel ths, Func condition) where TBuilderLevel : EmptyNinjaBuilder
      {
        ths.AddCondition(condition);
        return ths;
      }
    }
    
    public interface EmptyNinjaBuilder { void AddCondition(Func condition); Ninja Build(); }
    
    public interface Named_NinjaBuilder { TRemainder Named(string name); }
    public interface AtLevel_NinjaBuilder { TRemainder AtLevel(int level);}
    public interface WithShurikens_NinjaBuilder { TRemainder WithShurikens(int shurikenCount); }
    public interface WithSkill_NinjaBuilder { TRemainder WithSkill(Skill skillType); }
    
    // level one reductions
    public interface Sans_Named_NinjaBuilder :
      AtLevel_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_AtLevel_NinjaBuilder :
      Named_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_WithShurikens_NinjaBuilder :
      Named_NinjaBuilder,
      AtLevel_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_WithSkill_NinjaBuilder :
      Named_NinjaBuilder,
      AtLevel_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      EmptyNinjaBuilder { }
    
    // level two reductions
    // Named
    public interface Sans_Named_AtLevel_NinjaBuilder :
      WithShurikens_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_Named_WithShurikens_NinjaBuilder :
      AtLevel_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_Named_WithSkill_NinjaBuilder :
      AtLevel_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      EmptyNinjaBuilder { }
    // AtLevel
    public interface Sans_AtLevel_WithShurikens_NinjaBuilder :
      Named_NinjaBuilder,
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    public interface Sans_AtLevel_WithSkill_NinjaBuilder :
      Named_NinjaBuilder,
      WithShurikens_NinjaBuilder,
      EmptyNinjaBuilder { }
    // WithShurikens
    public interface Sans_WithShurikens_WithSkill_NinjaBuilder :
      Named_NinjaBuilder,
      AtLevel_NinjaBuilder,
      EmptyNinjaBuilder { }
    
    // level three reductions
    // Named
    public interface Sans_AtLevel_WithShurikens_WithSkill_NinjaBuilder :
      Named_NinjaBuilder,
      EmptyNinjaBuilder { }
    // AtLevel
    public interface Sans_Named_WithShurikens_WithSkill_NinjaBuilder :
      AtLevel_NinjaBuilder,
      EmptyNinjaBuilder { }
    // WithShurikens
    public interface Sans_Named_AtLevel_WithSkill_NinjaBuilder :
      WithShurikens_NinjaBuilder,
      EmptyNinjaBuilder { }
    // WithSkill
    public interface Sans_Named_AtLevel_WithShurikens_NinjaBuilder :
      WithSkill_NinjaBuilder,
      EmptyNinjaBuilder { }
    

提交回复
热议问题