Conditional Builder Method Chaining Fluent Interface

前端 未结 4 1557
清歌不尽
清歌不尽 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

    You could have a conditional optional parameter in your method that is true by default:

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

    This will of course be very specific to the WithSkill method:

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

    You could add it to other methods that you want to be conditional too.

    Another option is to have a method that nests the conditional parts of the builder:

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

    Then you can write it like this:

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

    Or like this:

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

    This is more generic and can be used with any methods of the builder.

    You can even add an optional "else":

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

    Or, as a "mixin":

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

    This is of course a way to create "if" statements as method calls. Other ways could also work:

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

    In this case, the builder keeps track whether it should ignore any method calls that are done in a "conditional" context if the condition is false. When would enter the context, End would exit it. You could also have an Otherwise() call to mark the "else" context. Interestingly enough, you could also cover other statements like this, like loops:

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

    In this case, the calls made in "loop" context must be recorded and played-back the desired number of times when End is called.

    So, contexts are a kind of state the builder can be at; they change how it operates. You could also nest contexts, using a stack to keep track of them. And you should check if certain call are valid at certain states and maybe throw exceptions if they aren't.

提交回复
热议问题