Conditional Builder Interface Chain Free Interface

I was wondering what would be the best way to implement the .When condition in a free interface using the method chain in the Builder object?

For example, how to implement the .WithSkill() and .When() methods in the following example:

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

Refresh . An approximate solution can be found here .

+44
c # design-patterns builder fluent-interface method-chaining
Apr 6 '12 at 17:00
source share
4 answers

What I would do is NinjaBuilder save the operations as a list of delegates, not apply them, and apply them only when calling .Build . This will allow you to make them conditional:

 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.Remove(builderActions.Length - 1); return this; } } 

Of course, this suggests that the condition is constant at the time the builder is created. If you want to make this inconsistent, you can do something like this:

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

If you want When be a slightly more compiler, you can make builderActions secure and do something like this:

 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 => condition() ? oldAction(n) : n; return this; } } 

and the original operations return ConditionalNinjaBuilder:

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

This way you can only call .When after the first call to another method. This has the added benefit / complication of a potential resolution for nested / aggravated conventions. Clap.

+70
Apr 6 2018-12-12T00:
source share

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

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

Let's also consider that you do not want to be able to call any method more than once.

eg.

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

This can be achieved with some serious surprise:

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

And then, mix it with Chris Shain's awesomeness to use the action stack!

I decided to implement it. Note that you cannot call any method twice with this solution chain. I put your When method as an extension method.

Here is the call 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 are my ninja classes and skills:

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

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

 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 { } 
+8
Apr 6 2018-12-12T00:
source share

You might consider writing overloaded versions of C, and in the second, where, as an argument:

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

Of course, this is due to the fact that you are going to write "Where" as a separate object as a whole, it looks something like this:

 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(); } } 
+5
Apr 6 2018-12-12T00:
source share

You may have a conditional optional parameter in your default true method:

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

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

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

You can add it to other methods, which should also be conditional.

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

 public NinjaBuilder When(bool condition, Action<NinjaBuilder> 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 general and can be used with any builder methods.

You can even add an optional "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; } 

Or, like "mixin" :

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

This, of course, is the way to create if statements as method calls. Other methods may also work:

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

In this case, the builder monitors whether any method calls that are executed in the conditional context should be ignored if the condition is false. When enters the context, End exits it. You can also call Otherwise() to mark the "else" context. Interestingly, you could also cover other statements like this, like loops:

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

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

Thus, contexts are a kind of state on which a builder can be; they change their behavior. You can also nest contexts using the stack to track them. And you should check if certain calls are valid in certain states and maybe throw exceptions if they are not.

+3
Apr 6 2018-12-12T00:
source share



All Articles