Schema del generatore nell'interfaccia. Cattiva decisione di progettazione?

5

Ho difficoltà a valutare un'implementazione del modello di build che ho appena creato.

Il contesto è una libreria API, quindi sto cercando di non esporre alcuna implementazione per avere un'interfaccia stabile e poter cambiare le implementazioni in seguito.

Il mio pensiero è che anche se c'è un riferimento all'implementazione nel file dell'interfaccia, l'interfaccia e l'implementazione non sono effettivamente accoppiate perché

In effect, a static nested class is behaviorally a top-level class...

Il vantaggio di questo design è che i client possono chiamare

Person person = new Person.Builder().age(20).firstName("John") .lastName("Doe").build();

senza bisogno di una classe di tipo Factory dedicata.

Mi manca qualcosa o si tratta di una decisione di progettazione valida?

Person.java

package net.mhi.rd;

public interface Person {

    int getAge();
    String getFirstName();
    String getLastName();

    public static class Builder {
        int age;
        String firstName;
        String lastName;

        public Builder age(int age) {
            this.age = age;
            return this;
        }

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

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

        public Person build() {
            return new PersonImpl(this);
        }
    }    
}

PersonImpl.java

package net.mhi.rd;

class PersonImpl implements Person {

    private final int age;
    private final String firstName;
    private final String lastName;

    PersonImpl(Person.Builder builder) {
        this.age = builder.age;
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
    }

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public String getFirstName() {
        return firstName;
    }

    @Override
    public String getLastName() {
        return lastName;
    }
}
    
posta mhi 14.07.2014 - 20:17
fonte

2 risposte

10

Il problema principale è che hai creato una dipendenza circolare tra Person e PersonImpl ... La persona non dovrebbe avere alcun riferimento alle classi di implementazione, ma la chiamata "new PersonImpl ()" nel builder crea un hard, compilazione -time dipendenza dalla classe concreta.

  • Penso che la soluzione sia far girare PersonBuilder nella propria classe, oppure spostare PersonBuilder in PersonImpl. È un po 'strano dichiarare una classe all'interno di un'interfaccia comunque - non direi che non l'ho mai fatto, ma sembra preferibile mantenere le interfacce semplici.

  • Il passaggio del Builder come argomento del costruttore in PersonImpl accoppia la classe di implementazione in modo molto preciso al builder. Faresti meglio a mantenere una separazione più pulita tra il costruttore e il dipendente. Il builder (ovviamente) deve conoscere PersonImpl, ma sarebbe meglio se PersonImpl non facesse riferimento al builder.

  • Ti aspetti di avere più di un'implementazione di Person? Sembra una classe di dati e non sono sicuro che sia necessario estrarre un'interfaccia in primo luogo.

  • E ... come schema generale, se creerai una dozzina di classi come questa, penso che questo modello sarebbe piuttosto ingombrante: c'è un'interfaccia, un'implementazione e un costruttore per ogni classe. Sembra un sacco di codice in più per qualcosa che potrebbe non richiedere interfacce distinte.

Ecco la variazione di Builder che Josh Block ha inventato. È simile in quanto utilizza una classe interna per accedere a & imposta i valori interni, in modo che la classe diventi effettiva e immutabile. Penso che la principale differenza con il tuo codice sia che non esiste un'interfaccia separata.

link

TL; DR :

  • le dipendenze dovrebbero andare in Builder - > Impl - > Interfaccia

  • omettendo l'interfaccia e annidando il Builder all'interno di Impl è un buon modo per fornire un accesso unico ai dettagli interni di impl, nascondendo una dipendenza circolare tra builder e impl.

risposta data 14.07.2014 - 20:33
fonte
3

Sono molto d'accordo con il paragrafo di apertura e il punto n. 3 di Rob. Ho intenzione di approfondire quelli perché sono a mio avviso i difetti di progettazione più seri.

Avresti davvero bisogno di creare un'interfaccia per Person se stai pensando di creare più di un tipo di implementazione di Person. Altrimenti, non c'è davvero alcun motivo per averlo. Qualcuno potrebbe obiettare che l'interfaccia Person consente di modificare la classe PersonImpl senza influire sul resto del programma. Più specificamente, l'interfaccia impedisce a qualsiasi parte esterna del programma di comunicare direttamente con PersonImpl, che sappiamo essere utile per un accoppiamento lento. Tuttavia, se ci sarà solo un'implementazione dell'interfaccia Person, allora significherebbe che non c'è alcun scopo nell'averlo e sarebbe solo un codice extra. Il codice aggiuntivo aumenta la complessità e l'aumento della complessità rende il codice più difficile da leggere, comprendere e mantenere.

Partendo dal presupposto che stai pianificando di avere più di un tipo di persona, avere un riferimento esplicito alla classe di implementazione nella tua interfaccia è un errore perché rende difficile estendere l'interfaccia alle nuove implementazioni. Ricorda sempre perché stai facendo quello che stai facendo. Nel caso della progettazione di interfacce, cerchiamo sempre di ottenere una maggiore flessibilità e manutenibilità assicurando che raramente dobbiamo modificare il codice esistente, poiché la modifica del codice esistente di solito porta all'introduzione di bug. Nella tua interfaccia, hai specificato PersonImpl nel metodo build (). Questo è un problema perché ogni volta che si desidera realizzare una nuova implementazione dell'interfaccia Person è necessario modificare il codice nella classe di Interface. In questo caso, potrebbe non essere un problema perché non c'è molto codice, ma man mano che il codice cresce, è molto facile che qualcosa del genere si perda nel codice.

Come nota personale, ti consiglio di integrare il codice con UML o una rappresentazione grafica simile durante la comunicazione le tue idee di design. Sono progettati specificamente per quello scopo e in quasi tutti i casi mi hanno aiutato a identificare difetti di progettazione nel mio codice che altrimenti non avrei identificato.

    
risposta data 24.05.2017 - 20:24
fonte

Leggi altre domande sui tag