条件构build器方法链接Fluent接口

我想知道什么是最好的方式来实现一个stream利的接口使用方法链接在一个Builder对象。

例如,我将如何在下面的示例中实现.WithSkill().WithSkill() .When()方法:

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

更新 – 一个样本解决scheme可以在这里find。

我要做的就是让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.Remove(builderActions.Length - 1); return this; } } 

当然,这是假定在build造者创build时条件是不变的。 如果你想使它不是恒定的,你可以这样做:

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

如果你想要更多的编译器检查,你可以使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 => condition() ? oldAction(n) : n; return this; } } 

并有原始的操作返回一个ConditionalNinjaBuilder:

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

那样你只能先调用另一个方法。 这也具有潜在地允许嵌套/复合条件的附加优点/复杂性。 让人惊讶。

我有一个接口链接解决scheme; 我的解决scheme唯一的问题是,它随着您想要支持的每种新方法的复杂性(规模)而增长。 但是,它为用户提供了非常棒的API。

让我们考虑你有三种方法,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> { } 

然后,与Chris Shain的迷人混合使用一堆行动!

我决定实施它。 请注意,您现在无法使用此链接解决scheme调用任何方法两次。 我把你的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 { } 

你可以考虑编写With的重载版本,然后在第二个地方作为参数:

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

你可以在你的方法中有一个条件可选参数,默认情况下是true

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

这当然会对WithSkill方法非常具体:

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

你可以把它添加到你想成为有条件的其他方法。

另一个select是有一个嵌套构build器的条件部分的方法:

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

这是更通用的,可以用于build设者的任何方法。

你甚至可以添加一个可选的“其他”:

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

或者,作为“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 ... 

这当然是创build方法调用“if”语句的一种方法。 其他方式也可以工作:

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

在这种情况下,如果条件为false,构build器将跟踪是否应该忽略在“条件”上下文中完成的任何方法调用。 When进入上下文, End会退出。 您也可以通过Otherwise()来标记“其他”上下文。 有趣的是,你也可以覆盖像这样的其他语句,比如循环:

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

在这种情况下,在“循环”环境中进行的调用必须被logging,并在调用End时回放所需的次数。

所以,情境是build筑者可以处在的一种状态 ; 他们改变它的运作方式。 你也可以嵌套上下文,使用堆栈来跟踪它们。 而且你应该检查某些电话在某些状态是否有效,如果不是,可能会引发exception。