c# design-patterns builder fluent-interface method-chaining

c# - Método de construcción condicional Chaining Fluent Interface



design-patterns builder (4)

Lo que haría es hacer que NinjaBuilder mantenga las operaciones como una lista de delegados, en lugar de aplicarlas, y solo aplicarlas cuando se .Build . Esto te permitiría hacerlos condicionales:

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

Por supuesto, esto supone que la condición es constante en el momento de la creación del generador. Si quieres que no sea constante, podrías hacer algo como esto en su lugar:

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

Si desea When se compruebe un poco más el compilador, puede proteger las acciones de constructor y hacer algo como esto:

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

y hacer que las operaciones originales devuelvan un ConditionalNinjaBuilder:

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

De esa forma solo puede llamar .When llame por primera vez a otro método. Esto tiene la ventaja / complicación adicional de potencialmente permitir condicionales anidados / compuestos. Yikes.

Me preguntaba cuál sería la mejor manera de implementar una condición .When en una interfaz fluida utilizando el método de encadenamiento en un objeto de Builder .

Por ejemplo, ¿cómo implementaría los .WithSkill() y .When() en el siguiente ejemplo:

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

Actualización : se puede encontrar una solución de muestra here .


Podría considerar escribir versiones sobrecargadas de Con, y en el segundo, tomar un Where como argumento:

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

Por supuesto, esto se basa en la noción de que vas a escribir Where como un objeto separado por completo, que esencialmente se ve así:

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


Podría tener un parámetro opcional condicional en su método que sea true de manera predeterminada:

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

Por supuesto, esto será muy específico para el método WithSkill :

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

Podría agregarlo a otros métodos que quiera que sean condicionales también.

Otra opción es tener un método que anide las partes condicionales del generador:

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

Entonces puedes escribirlo así:

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

O así:

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

Esto es más genérico y se puede usar con cualquier método del constructor.

Incluso puede agregar un "else" opcional:

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

O, como un "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 ...

Esta es, por supuesto, una forma de crear sentencias "if" como llamadas a métodos. Otras formas también podrían funcionar:

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

En este caso, el constructor realiza un seguimiento de si debe ignorar cualquier llamada de método que se realice en un contexto "condicional" si la condición es falsa. When entraría en el contexto, End lo abandonaría. También podría tener una llamada a Otherwise() para marcar el contexto "else". Curiosamente, también podría cubrir otras declaraciones como esta, como bucles:

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

En este caso, las llamadas realizadas en el contexto de "bucle" deben grabarse y reproducirse el número deseado de veces cuando se invoca End .

Entonces, los contextos son un tipo de estado en el que el constructor puede estar; ellos cambian como opera También puede anidar contextos, utilizando una pila para hacer un seguimiento de ellos. Y debe verificar si ciertas llamadas son válidas en ciertos estados y tal vez arrojar excepciones si no lo son.


Tengo una solución para el encadenamiento de interfaz; el único problema con mi solución es que crece en complejidad (escala) con cada nuevo método que quiera admitir. Pero, hace una API realmente increíble para el usuario.

Consideremos que tiene 3 métodos, A, B y C, y desea usarlos en una cadena.

Permítanos también considerar que no desea poder llamar a ningún método más de una vez.

p.ej

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

Esto se puede lograr con una genialidad seria:

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

Y luego, ¡mezcla esto con la genialidad de Chris Shain para usar una pila de acciones!

Decidí implementarlo. Tenga en cuenta que no puede llamar a ningún método dos veces ahora con esta solución de encadenamiento. Puse tu método When como método de extensión.

Aquí está el código de llamada:

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

Aquí están mis clases de Ninja y Habilidad:

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 }

Esta es la clase 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; } }

Y el resto de este código está solo por encima para que las conversiones y llamadas funcionen:

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