Esiste un nome per il Builder Pattern in cui il Builder è implementato tramite interfacce, quindi sono richiesti determinati parametri?

4

Quindi abbiamo implementato il modello di builder per la maggior parte del nostro dominio per aiutare a comprendere cosa effettivamente viene passato a un costruttore e per i normali vantaggi offerti da un builder. L'unica differenza era che abbiamo esposto il builder attraverso le interfacce in modo da poter concatenare le funzioni richieste e le funzioni non richieste per garantire che i parametri corretti fossero passati. Ero curioso di sapere se esistesse uno schema come questo.

Esempio di seguito:

public class Foo
{
   private int someThing;
   private int someThing2;
   private DateTime someThing3;

   private Foo(Builder builder)
   {
       this.someThing = builder.someThing;
       this.someThing2 = builder.someThing2;
       this.someThing3 = builder.someThing3;
   }

   public static RequiredSomething getBuilder()
   {
       return new Builder();
   }

   public interface RequiredSomething { public RequiredDateTime withSomething (int value); }
   public interface RequiredDateTime { public OptionalParamters withDateTime (DateTime value); }
   public interface OptionalParamters { 
      public OptionalParamters withSeomthing2 (int value); 
      public Foo Build ();}

   public static class Builder implements RequiredSomething, RequiredDateTime, OptionalParamters 
   {
      private int someThing;
      private int someThing2;
      private DateTime someThing3;

      public RequiredDateTime withSomething (int value) {someThing = value; return this;}
      public OptionalParamters withDateTime (int value) {someThing = value; return this;}
      public OptionalParamters withSeomthing2 (int value) {someThing = value; return this;}
      public Foo build(){return new Foo(this);}
   }
}

Esempio di come viene chiamato:

   Foo foo = Foo.getBuilder().withSomething(1).withDateTime(DateTime.now()).build();
   Foo foo2 = Foo.getBuilder().withSomething(1).withDateTime(DateTime.now()).withSomething2(3).build();
    
posta Zipper 28.10.2013 - 02:29
fonte

5 risposte

4

Quello che stai facendo è chiamato fluente interfaccia .

Dall'articolo di Fowler:

In essence we create the various objects and wire them up together. If we can't set up everything in the constructor, then we need to make temporary variables to help us complete the wiring - this is particularly the case where you're adding items into collections.

Che è dove @Frank stava parlando di "Tipi di fantasma" . Anche Fowler dice

Probably the most important thing to notice about this style is that the intent is to do something along the lines of an internal DomainSpecificLanguage. Indeed this is why we chose the term 'fluent' to describe it, in many ways the two terms are synonyms. The API is primarily designed to be readable and to flow.

Il modo in cui ho visto questo discusso è usare "fluente" come aggettivo, quindi nel tuo caso sarebbe un "costruttore fluente".

    
risposta data 26.10.2015 - 15:57
fonte
3

Quello che stai facendo è essenzialmente limitare i metodi disponibili tramite il sistema di tipi. Il sistema dei tipi di Java è sfortunatamente un po 'debole, quindi sei piuttosto limitato sotto questi aspetti.

Quando osservi sistemi di tipi più potenti in linguaggi come Haskell o Scala, troverai il termine programmazione a livello di carattere . Nella sua forma completa, la programmazione a livello di codice può effettivamente comportare calcoli eseguiti in fase di compilazione. Tuttavia, una forma meno rigorosa di essa sta sfruttando il sistema dei tipi per rendere espliciti i progetti impliciti, che è più spesso definito come lavorare con tipi di fantasma .

Ad esempio, spesso i progetti richiedono che un utente chiami prima il metodo a , prima di chiamare il metodo b di un oggetto. Nei sistemi di tipo più deboli, devi dire ai tuoi utenti tramite commenti o documenti di progettazione. Ma tecnicamente, potrebbero ancora chiamare b prima, quindi potresti dover aggiungere un controllo di qualche tipo, quindi lanciare un'eccezione o qualcosa del genere. Un sacco di lavoro, e un sacco di potenziali di errore - che ovviamente hai già riconosciuto.

I tipi di fantasma d'altra parte sono usati per trasformare queste dipendenze implicite tra le chiamate di metodo in una dipendenza dipendente dalla compilazione esplicita spostando i diversi metodi in diversi tipi.

Qui è un bell'esempio di tipi di fantasma in Java e < a href="http://james-iry.blogspot.de/2010/10/phantom-types-in-haskell-and-scala.html"> qui 'è un semplice esempio che li confronta in Haskell e Scala . In sostanza, ciò che ottengono questi esempi è la codifica di uno stato nel sistema di tipi. Il secondo articolo, f.ex., costruisce oggetti razzi, che possono essere solo launch() ed, se sono stati riempiti con carburante e O2, cioè puoi chiamare quel metodo solo se hai chiamato altri metodi prima di esso. A differenza di una soluzione, che implementa semplicemente tutti questi metodi in una classe, l'utilizzo di tipi di fantasmi consente al compilatore di controllarlo automaticamente.

A sua volta, ciò significa anche che in un IDE le varianti di completamento automatico saranno limitate a quelle che hanno senso. Se hai un oggetto razzo che non è stato ancora rifornito, il tuo completamento automatico semplicemente non ti mostrerà un metodo launch() .

Per tornare al tuo esempio, puoi sostituire il metodo launch() con il metodo build() e pensare alle diverse varianti possibili per le quali un processo di compilazione è fattibile. Quindi definisci i tuoi tipi di fantasma in base agli elementi che devono essere comunicati al costruttore e scopri se c'è un ordine o se ci sono più aspetti indipendenti. Nei due post a cui ti ho collegato, puoi vedere un esempio con un solo aspetto (il piano è in volo o atterrato) e con due aspetti indipendenti (il razzo viene riempito con carburante e O2).

Questo approccio è generalmente fattibile per un numero qualsiasi di aspetti, ma quando li codifichi nel sistema dei tipi, non raccomanderei di andare troppo oltre. In termini di qualità / leggibilità del codice si riferisce a metodi con molti parametri. Abbiamo riscontrato che questi non sono ottimali e causano più problemi di quanti ne valga. Allo stesso modo, i tipi di fantasma codificati in parametri di tipo significa che si finisce con molti e molti parametri di tipo a costo di leggibilità. Si noti che questo si applica solo agli aspetti indipendenti che vengono codificati come tipi fantasma. Dopo averne dipendenti, vengono codificati tramite lo stesso parametro di tipo.

    
risposta data 28.10.2013 - 07:35
fonte
2

Il modello è ufficialmente chiamato Step Builder , che rende utilizzo di un'interfaccia fluente.

    
risposta data 27.03.2018 - 07:56
fonte
0

Come indicato sopra, quello che stai facendo è un esempio di interfaccia fluente. Uso il pattern che stai utilizzando un po 'per i vari wrapper che uso nei miei test di integrazione. Lo chiamo "Fluent Expression Builder". I vantaggi sono che il codice risultante è estremamente leggibile e autodocumentante. Offre anche vantaggi significativi per la composizione di un nuovo codice utilizzando queste API poiché le funzionalità dell'interfaccia sono auto-individuabili e autodocumentanti. Cioè non è necessario sapere se un parametro è richiesto o meno - l'interfaccia garantirà di includerlo assicurandosi che ad un certo punto nell'espressione che specifica tale parametro sia l'unico metodo disponibile.

Ecco un grande articolo che lo descrive da Per Lundholm: link

    
risposta data 04.10.2016 - 22:09
fonte
0

Is there a name for the Builder Pattern where the Builder is implemented via interfaces so certain parameters are required?

Breve storia lunga. No non c'è . Indipendentemente dallo sintassi dello zucchero , la verità è che nessuna interfaccia costringe i consumatori a chiamare quei metodi intitolati come "richiesti". O chiamarli in un ordine specifico. Ci vorrebbe per documentare bene il costruttore e le interfacce per il consumatore di essere a conoscenza dei vincoli del costruttore.

Potremmo convalidare gli argomenti subito dopo aver chiamato il metodo .build() , ma ancora, il consumatore potrebbe decidere di ignorare i metodi. 1 .

So we implemented the builder pattern for most of our domain to help in understandability of what actually being passed to a constructor, and for the normal advantages that a builder gives.

I costruttori non ti aiuteranno per quello scopo. Inoltre, mancano di un po 'di "chiarezza" poiché sono in grado di consentire ai consumatori di creare istanze di oggetti con diverse e possibili combinazioni (permutazioni) di attributi e senza alcun ordine. Sono anche bravi a nascondere gli attributi richiesti poiché (comunemente) usano valori predefiniti per completare quegli attributi che erano nascosti intenzionalmente.

Al momento della costruzione (quando viene chiamato build() ), letteralmente, i consumatori non conoscono completamente lo stato del nuovo oggetto. Tieni presente che chiunque abbia creato e utilizzato il builder potrebbe o potrebbe non essere lo stesso che attiva .build() . Il consumo e l'edificio potrebbero accadere in momenti diversi e in luoghi diversi.

Se mi venisse chiesto di ottenere "chiarezza", preferirei avere fiducia nelle fabbriche. Ad esempio, nel schema dei metodi di fabbrica o modello di fabbrica astratto . Tuttavia, dipenderebbe da quale modello si adatta meglio alle mie esigenze di creazione di oggetti.

Potresti trovare interessante questo QUINTA domanda e le sue risposte.

1: Ovviamente, la compilazione fallirà ripetutamente, finché il consumatore non deciderà di chiamare quei metodi. Ma poi, il costruttore non farà un grosso problema.

    
risposta data 27.03.2018 - 09:17
fonte

Leggi altre domande sui tag