2012-08-31 5 views
5

Ho creato un modello di stile fluente per il builder che aiuta a caricare i miei dati. L'ordine di determinati metodi è importante e si chiedeva quale sia il metodo preferito per gestire la sequenza corretta.Qual è il modo migliore di gestire l'ordine nel modello di builder?

Ho il seguente momentaneamente:

using NUnit.Framework; 

[TestFixture] 
public class DataBuilderTests 
{ 
    [Test] 
    public void Can_NAME() 
    { 
     new DataLoader() 
      .Start() // must be called first 
      .Setup() // then called next 
      .LoadEmployees() // optional order not NB 
      .LoadProducts() // optional order not NB 
      .StartCleanup() // begin cleanup 
      .CleanupEmployees() // optional order not NB 
      .CleanupProducts() // optional order not NB 
      .End(); 
    } 
} 

public class DataLoader 
{ 
    public DataBuilderSetup Start() 
    { 
     return new DataBuilderSetup(this);  
    } 
} 

public class DataBuilderSetup 
{ 
    private readonly DataLoader _dataLoader; 

    public DataBuilderSetup(DataLoader dataLoader) 
    { 
     _dataLoader = dataLoader; 
    } 

    public DataBuilderOptions Setup() 
    { 
     // do setup 
     return new DataBuilderOptions(_dataLoader); 
    } 
} 

public class DataBuilderOptions 
{ 
    private readonly DataLoader _dataLoader; 

    public DataBuilderOptions(DataLoader dataLoader) 
    { 
     _dataLoader = dataLoader; 
    } 

    public DataBuilderOptions LoadEmployees() 
    { 
     // load 
     return this; 
    } 

    public DataBuilderOptions LoadProducts() 
    { 
     // load 
     return this; 
    } 

    public DataBuilderCleanupOptions StartCleanup() 
    { 
     return new DataBuilderCleanupOptions(_dataLoader); 
    } 
} 

public class DataBuilderCleanupOptions 
{ 
    private readonly DataLoader _dataLoader; 

    public DataBuilderCleanupOptions(DataLoader dataLoader) 
    { 
     _dataLoader = dataLoader; 
    } 

    public DataBuilderCleanupOptions CleanupEmployees() 
    { 
     // cleanup 
     return this; 
    } 

    public DataBuilderCleanupOptions CleanupProducts() 
    { 
     // cleanup 
     return this; 
    } 

    public DataLoader End() 
    { 
     return _dataLoader; 
    } 
} 
+0

Cosa c'è che non va nella tua attuale soluzione? –

+0

Niente, solo qualcosa che mi è venuto in mente stamattina ed ero curioso di sapere come gli altri hanno gestito lo scenario – Chev

+0

Si deve chiamare 'CleanupEmployees' se viene chiamato' LoadEmployees'? –

risposta

0

Il metodo preferito è quello di evitare ad ogni costo. Progetta il tuo costruttore in modo che sia chiaro cosa deve essere fatto.

ObjectBuilder 
.Init() 
.Optional1() 
.Optional3() 
.Optional2() 
.ToObject() 

Se qualcosa deve accadere per prima cosa, fatelo nel costruttore o nel metodo di fabbrica. Quindi avere sempre un metodo che completa il processo di compilazione, la pulizia e tutto.

+0

Lo faccio al momento, ma immagina uno scenario in cui una scelta può esistere solo se viene utilizzata una selezione precedente. Lo scenario tipico è una clausola OrderBy comune con procedimento ASC o DESC. Queste opzioni esistono solo se OrderBy è selezionato. – Chev

+0

@Chev - Questo è in realtà uno scenario molto diverso. Quello che descrivi è in realtà concatenare i costruttori. In tal caso non sarebbe possibile aggiungere un nuovo predicato in quanto si ha a che fare con un nuovo builder. Il percorso di costruzione rimarrebbe inequivocabile. – ChaosPandion

1

È possibile aggiungere un membro di coda privato al builder, ad esempio Queue<string> e aggiungere nomi dell'operazione a ogni passo del builder.

.Build() metodo o nel tuo caso .End() controllerà la coda per contenere i nomi di operazione corretti in un ordine corretto. Altrimenti puoi lanciare un InvalidOperationException

Inoltre puoi usare un albero come struttura dati. Una struttura ti consentirà di sezionare le opzioni non disponibili in base ai precedenti passaggi del builder.

Ma meglio considerare altre risposte in quanto il mio approccio è in realtà un trucco e creerà un problema di manutenzione.

2

Una parte dei punti di forza di BuilderPattern è che può proteggere i consumatori dall'ordinamento "magico" imposto delle chiamate di metodo.

lo consiglio cambiare il vostro disegno in modo che sia:

  • Tutti gli argomenti necessari sono forniti in anticipo per proteggere i consumatori dalla forzata ordinato chiamate a Start e Setup.
  • Modificare le responsabilità delle entità in modo che possano essere compilate in modo arbitrario.

Ovviamente questo è il mio preferenza personale. Se questo tipo fa parte di una libreria che verrà utilizzata da terze parti, ti consiglio vivamente di rimuovere la necessità di ordinare i metodi magici. Se è probabile che venga utilizzato in casa, spetta a te valutare i costi associati alla modifica del codice, altrimenti non farlo.

+0

È interessante notare come siano iniziate le mie riflessioni su librerie di terze parti come FluentNHibernate, FluentValidation e NHibernate stessa che sembrano farlo. – Chev

+0

D'accordo, sconfigge uno degli scopi principali di un costruttore di dover chiamare i metodi in un certo ordine solo per configurarlo. Il builder deve memorizzare nella cache le parti e quindi riordinarle nella chiamata di compilazione finale. – tcarvin

0

In Java (C# e la sua ereditarietà multipla non dovrebbe rendere diverso) si potrebbe fare in questo modo:

dichiarare insieme di interfacce, che contengono un metodo solo:

Interface DoFirstThing { // could be renamed to "BuilderOnStart" or "BuilderStartingState" 
    DoSecondThing doFirst(); 
} 

Interface DoSecondThing { 
    DoLastThing doSecond(); 
} 

Interface DoLastThing { 
    BuilderReady doLast(); 
} 

Interface BuilderReady { 
    Result build(); 
} 

class BuilderWithForcedSequence implements DoFirstThing, DoSecondThing, DoLastThing, BuilderReady { 

    // implement all 

} 

e infine, si avrebbe bisogno di qualche metodo in fabbrica o in fabbrica per impostare lo stato iniziale per questo costruttore:

public DoFirstThing createNewBuilderWithForcedSequence(requiredParameters){ 
    return new BuilderWithForcedSequence(requiredParameters); 
} 

Questo produrrebbe Builder, con ordinamento forzata di costruire meth Ods (dovrebbero essere rinominati da doThat a qualcosa di significativo), che può chiamare solo doFirst(), dopo quello doSecond() ... e così via, fino allo stato finale, quando l'oggetto sarebbe costruito, utilizzando il metodo build().

Result result = builder.doFirst().doSecond().doLast().build(); 


EDIT:
Ops, che è abbastanza preciso il vostro approccio :)

0

La soluzione attuale è l'approccio che avrei preso per fornire una sintassi fluente, anche se non necessariamente dire esattamente segue il modello del costruttore. In sostanza, ciò che stai facendo è incatenare i costruttori insieme alle restrizioni fornite da una macchina a stati. Vedo ben poche differenze in ciò che si sta facendo rispetto a qualsiasi altra configurazione fluente comunemente accettata, come ad esempio un hibernate fluente o affermazioni fluenti.

Problemi correlati