2012-04-06 24 views
46

mi chiedevo cosa sarebbe il modo migliore per attuare una condizione .When in un fluente un'interfaccia utilizzando metodo concatenamento in un oggetto Builder?condizionale Metodo Builder concatenamento Fluent interfaccia

Per esempio come vorrei attuare le .WithSkill() e .When() metodi nel seguente esempio:

var level = 5; 

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

Aggiornamento - Una soluzione del campione può essere trovato here.

risposta

70

quello che mi piacerebbe fare è avere NinjaBuilder mantenere le operazioni come un elenco di delegati, piuttosto che la loro applicazione, e si applicano solo quando .Build si chiama. Ciò consentirebbe di fare loro condizionale:

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

Naturalmente, questo presuppone che la condizione è costante al momento della creazione del costruttore. Se si vuole rendere non costante, si potrebbe fare qualcosa di simile a questo, invece:

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

Se volete When essere un po 'più compilatore controllato, è possibile effettuare builderActions protette e fare qualcosa del genere:

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

e hanno le operazioni originali restituiscono un ConditionalNinjaBuilder:

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

In questo modo è possibile chiamare solo .When dopo la prima chiamata un altro metodo. Questo ha l'ulteriore vantaggio/complicazione di consentire potenzialmente anche condizionali nidificati/composti. Yikes.

+2

Nota che ora fai esplicito presupposto quando puoi usare "Quando" - solo dopo la tua condizione. Non si applica questo, quindi potrebbe essere soggetto a errori. Anche le tue firme di metodo correnti sono errate - ognuna dovrebbe restituire un 'NinjaBuilder', altrimenti questa non è un'interfaccia fluente – BrokenGlass

+0

Sistemati i sigs del metodo. Avrò una pugnalata nel renderlo esplicito. –

+0

Fammi sapere come va - Ho usato diverse interfacce (e metodi di estensione corrispondenti) per far rispettare l'ordine in passato, sarei felice di vedere qualcosa di più elegante – BrokenGlass

3

Si potrebbe avere un parametro opzionale condizionale nel metodo che è true di default:

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

Questo sarà ovviamente molto specifico al metodo WithSkill:

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

si potrebbe aggiungere che ad altri metodi che si desidera essere anche condizionali.

Un'altra opzione è quella di avere un metodo che nidifica le parti condizionali del costruttore:

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

Poi si può scrivere in questo modo:

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

O come questa:

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

Questo è più generico e può essere utilizzato con qualsiasi metodo del costruttore.

È anche possibile aggiungere un elemento "altro":

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

Oppure, come "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 ... 

Questo è naturalmente un modo per creare "if" come chiamate di metodo. Altri modi potrebbe anche funzionare:

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

In questo caso, il costruttore registra se deve ignorare qualsiasi chiamate di metodo che sono state svolte in un contesto "con riserva" se la condizione è falsa. When entrerebbe nel contesto, End uscirà. Potresti anche avere una chiamata Otherwise() per contrassegnare il contesto "else". È interessante notare che, si potrebbe coprire anche altre dichiarazioni di questo tipo, come i cicli:

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

In questo caso, le chiamate effettuate in un contesto "loop" devono essere registrati e riprodotti-back il numero desiderato di volte in cui End viene chiamato .

Quindi, i contesti sono una sorta di stato il builder può essere a; cambiano il modo in cui opera. Puoi anche nidificare i contesti, usando una pila per tenerne traccia. E dovresti controllare se certe chiamate sono valide in determinati stati e magari generare eccezioni se non lo sono.

5

Si potrebbe non crei versioni di overload di Con, e nel secondo, prendere un caso come argomento:

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

Naturalmente, questo si basa sul concetto che si sta andando a scrivere Dove come un oggetto separato del tutto, che sembra sostanzialmente simile a questo:

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

} 
8

Ho una soluzione per interfacciare il concatenamento; l'unico problema con la mia soluzione è che cresce in complessità (scala) con ogni nuovo metodo che si desidera supportare. Ma rappresenta un'API davvero fantastica per l'utente.

Consideriamo che avete 3 metodi, A, B e C, e volete usarli in una catena.

Consideriamo inoltre che non si desidera poter chiamare alcun metodo più di una volta.

ad es.

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

Questo può essere realizzato con una certa suggestione serio:

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

E poi, mescolare questo con suggestione di Chris Shain di utilizzare una pila di azioni!

Ho deciso di implementarlo. Nota che non puoi chiamare due volte nessun metodo con questa soluzione di concatenazione. Ho inserito il tuo metodo When come metodo di estensione.

Ecco il codice chiamante:

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

ecco la mia Ninja e abilità classi:

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 
} 

Questa è la classe 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; 
    } 
} 

e il resto di questo il codice è solo un overhead per far funzionare le conversioni e le chiamate:

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

Questo è davvero fantastico, specialmente se combinato con ciò che è stato scritto per jOOQ su http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/ che mostra come implementare qualsiasi lingua normale utilizzando le interfacce. Nota che non hai bisogno di questa esplosione dell'interfaccia, hai solo bisogno di un'interfaccia per ogni transizione nella macchina a stati rappresentata dalla tua lingua normale. –