Possibile modo di rendere il generatore di classi java più astratto usando le chiavi richieste dell'interfaccia

1

Sto cercando un modello più astratto per i costruttori che gestiscono le richieste campi senza la necessità di scrivere un validatore che controlli se tutti i campi richiesti sono impostati.

Mi piace questo costruttore. Ma c'è un modo per rendere le interfacce più astratte in modo che non sia necessario creare una nuova interfaccia per ogni campo richiesto?

public class Example {

    private String first;
    private String second;
    private String third;
    private String fourth;
    private String fifth;

    public static RequiredSecond builder(String first) {
        return new ExampleBuilder(first);
    }

    public interface RequiredSecond {
        RequiredThird withSecond(String second);
    }

    public interface RequiredThird {
        RequiredFourth withThird(String third);
    }

    public interface RequiredFourth {
        Build withFourth(String fourth);
    }

    public interface Build {
        Build withFifth(String fifth);

        Example build();
    }

    private static class ExampleBuilder implements RequiredSecond, RequiredThird, RequiredFourth, Build {
        Example example = new Example();

        public ExampleBuilder(String first) {
            example.setFirst(first);
        }

        @Override
        public Build withFifth(String fifth) {
            example.setFifth(fifth);
            return this;
        }

        @Override
        public Example build() {
            return example;
        }

        @Override
        public Build withFourth(String fourth) {
            example.setFourth(fourth);
            return this;
        }

        @Override
        public RequiredFourth withThird(String third) {
            example.setThird(third);
            return this;
        }

        @Override
        public RequiredThird withSecond(String second) {
            example.setSecond(second);
            return this;
        }
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }

    public String getThird() {
        return third;
    }

    public void setThird(String third) {
        this.third = third;
    }

    public String getFourth() {
        return fourth;
    }

    public void setFourth(String fourth) {
        this.fourth = fourth;
    }

    public String getFifth() {
        return fifth;
    }

    public void setFifth(String fifth) {
        this.fifth = fifth;
    }

    @Override
    public String toString() {
        return "Example [first=" + first + ", second=" + second + ", third=" + third + ", fourth=" + fourth +
                ", fifth=" + fifth + "]";
    }

}
public class Labb {

    public static void main(String[] args) {
        // All required including optional
        Example ex1 = Example.builder("first").withSecond("second").withThird("third").withFourth("fourth").withFifth("optional fith").build();
        System.out.println(ex1);
        // Output: Example [first=first, second=second, third=third, fourth=fourth, fifth=optional fith]

        // All required excluding optional
        Example ex2 = Example.builder("first").withSecond("second").withThird("third").withFourth("fourth").build();
        System.out.println(ex2);
        // Output: Example [first=first, second=second, third=third, fourth=fourth, fifth=null]
    }
}
    
posta heldt 13.02.2015 - 12:58
fonte

6 risposte

2

Secondo Joshua Bloch, autore di Effective Java, l'articolo 2 dice fondamentalmente che ci sono tre modi comuni per creare oggetti nella lingua:

  1. Costruttori sovraccaricati dall'utente per impostare i campi obbligatori e mutatori per impostare campi facoltativi.

    public class Example {
      private String first;
      private String second;
    
      //first is only required field
      public Example(final String first){
        this.first = first;
      }
    
      //Can't make a constructor just for second field
    
      //first and second are both required fields
      public Example(final String first, final String second){
        this.first = first;
        this.second = second;
      }
      //mutators for optional fields
    }
    
  2. Usa i mutatori e gli accessor in stile JavaBeans, non preoccupati troppo per l'applicazione della mutevole in fase di compilazione.

    public class Example {
      private String first;
    
      public void setFirst(final String first){
        this.first = first;
      }
    
      public String getFirst(){
        return first;
      }
    }
    
  3. Utilizza lo schema di progettazione Builder

    public class Example {
      private String first;
      private String second;
    
      private Example(Builder builder){
        this.first = builder.first;
        this.second = builder.second;
      }
    
      @Override
      public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("Example [first=").append(first).append(", second=").append(second).append("]");
        return sb.toString();
      }
    
      //Add getter methods as needed, but no setters if you want immutable objects
    
      public static class Builder{
        private String first;
        private String second;
    
        //required fields go in the constructor
        public Builder(final String first){
          this.first = first;
        }
    
        //optional fields have setter type methods
        public Builder second(String value){
          this.second = value;
        }
    
        public Example build(){
          return new Example(this);
        }
      }
    
      public static void main(String[] args){
        //builder with required first field
        Builder builder = new Example.Builder("first");
        //optional second field - calls can be chained
        builder.second("second");
        Example example = builder.build();
        System.out.println(example.toString());
      }
    }
    

Il vantaggio principale dell'utilizzo del pattern Builder è che tutti gli oggetti creati, le istanze di Example in questo caso, possono essere garantiti immutabili in fase di compilazione.

Non mi è chiaro il motivo per cui hai un'interfaccia per ogni campo nell'oggetto finale costruito, né perché sono interfacce pubbliche. Questo mi sembra errato, ma non ne sono sicuro.

    
risposta data 20.02.2016 - 16:38
fonte
1

Con il tuo design, il programmatore deve chiamare tutti i tuoi metodi nell'ordine che definisci. In questo caso, se hai solo pochi parametri, un costruttore dovrebbe essere perfetto.

Quello che preferisco nel modello di builder è che puoi chiamare i metodi nell'ordine desiderato dallo sviluppatore.

Il metodo build dovrebbe convalidare i valori richiesti.

public class Builder {
    private String first;
    ...

    public Builder first(String first) {
        this.first = first;
        return this;
    }

    public Example build() {
        if(first == null) throw new IllegalArgumentException("first can't be null");
        return new Example(this.first);
    }
    
risposta data 02.12.2015 - 15:23
fonte
1

Aggiungerò alla risposta di @ kpw con una mia abitudine che potrebbe essere istruttiva / utile. Se un campo è veramente richiesto nel tuo oggetto, allora dovrebbe essere richiesto nel costruttore.

In generale, non mi piace la tendenza di avere Builder di classe per tutto, ma sono strongmente a favore dello stile "incatenato" o "fluido" dell'invocazione di metodo che presentano. Progettando la tua classe come il suo generatore, ottieni le belle caratteristiche di entrambi i modelli di progettazione, in una classe.

public class Thing
{
    protected String one = null ; // required
    protected String two = null ; // optional

    private Thing() {} // Explicitly prevent construction without "one"

    public Thing( String one )
    {
        this.setOne( one ) ;
    }

    public String getOne()
    { return this.one ; }

    public Thing setOne( String s )
    {
        if( s == null ) throw new IllegalArgumentException() ;
        this.one = s ;
        return this ;
    }

    public String getTwo()
    { return this.two ; }

    public Thing setTwo( String s )
    {
        this.two = s ;
        return this ;
    }

    public SomeReturnedStuff doStuff()
    {
        // is guaranteed to have this.one
        // optionally acts on this.two
    }
}

Utilizzo di questo modello:

Thing nope = new Thing() ; // forbidden at compile time
Thing nopeAgain = new Thing(null) k // throws exception at runtime
Thing alpha = new Thing( "un" ) ;
alpha.doStuff() ; // does stuff with "un"
Thing beta = new Thing( "uno" )
        .setTwo( "dos" ) ;
beta.doStuff() ; // does stuff with "deux" and "dos"
alpha.setOne( "ichi" )
     .setTwo( "ni" )
     .doStuff()
     ;

Come mostra l'ultimo esempio, questo pattern consente anche di riutilizzare un'istanza "ricostruendola" con i propri metodi di mutatore.

YMMV ma io, per esempio, ho adottato questo schema universalmente. Buon divertimento!

    
risposta data 21.02.2016 - 17:11
fonte
0

I like this builder.

veramente hai bisogno di un Builder nel tuo caso? Il gradimento di un pattern non è sufficiente per giustificare il suo utilizzo.

Fino a 4-5 argomenti, la creazione di istanze della tua classe direttamente dal costruttore semplifica enormemente il tuo codice e ti consente di rendere la tua classe immutabile.

public final class Example {
    private final String first;
    private final String second;
    private final String third;
    private final String fourth;
    private final String fifth;

    public Example(final String first, final String second, final String third, final String fourth, final String fifth) {
        ...
    }

    @Override
    public final String toString() {
        return "Example [first=" + first + ", second=" + second + ", third=" + third + ", fourth=" + fourth + ", fifth=" + fifth + "]";
    }

    public final String getFirst() { 
        return first; 
    }

    ...
}
    
risposta data 02.09.2015 - 09:07
fonte
0

But is there a way to make the interfaces more abstract so that I don't need to create a new interface for each required field?

No. Quello che hai fatto qui è implementare una macchina a stati con l'applicazione del tempo di compilazione. Per farlo funzionare, avrai bisogno di un'interfaccia per ogni stato unico. Come minimo, richiederà uno stato per ogni campo obbligatorio.

    
risposta data 01.01.2016 - 18:25
fonte
0

Non vorrei toccare codici del genere con un palo da 10 piedi. Capisco che probabilmente stai cercando di risolvere qualche problema e questo forse ne è un riassunto. Ma se un codice viene contorto, probabilmente stai andando nella direzione sbagliata. La mia sensazione è che un costruttore dovrebbe essere sufficiente, e puoi verificare i campi richiesti nella funzione di costruzione. Puoi anche provare più funzioni di creazione:

...
class ExampleBuilder {
    private String first;
    ...
    public Example buildFirst() {
        if(first == null) throw new IllegalArgumentException("first can't be null");
        return new Example(this);
    }
    ...
}
    
risposta data 06.03.2015 - 01:44
fonte

Leggi altre domande sui tag