Utilizzo di Simple / Static Factory vs. istanziazione diretta: new Thing () vs. factory.createThing () - qual è il vantaggio?

5

Ecco due modi per creare un'istanza di un oggetto:

Opzione 1:

Thing thing = new Thing();

Opzione 2: (la fabbrica è una fabbrica statica, ma potrebbe anche essere una fabbrica semplice).

Thing thing = ThingFactory.createThing();

Dove:

class ThingFactory(){
    public static Thing createThing(){
        return new Thing();
    }
}

L'opzione 2 è considerata migliore, perché l'utilizzo di una fabbrica disaccoppia il codice cliente dall'implementazione concreta della classe che sta creando un'istanza.

Tuttavia, che differenza fa effettivamente se uso new Thing() o factory.createThing() ? L'unica differenza tra le due opzioni è che il codice di istanza viene spostato in un posto diverso.

Come ho spiegato sopra, conosco la spiegazione "accademica" del perché usare una fabbrica è "migliore", ma in realtà non ne vedo il vantaggio pratico. In entrambi i casi, il client sa esattamente cosa vuole: a Thing . L'unica differenza è l'esatta riga di codice: new Thing() o factory.createThing() .

Le fabbriche più sofisticate, posso vedere i loro benefici. Ad esempio, utilizzando il pattern Abstract Factory, potrei creare diverse sottoclassi di Factory che istanziano diverse implementazioni concrete di Thing e iniettare l'adatto Factory al client in fase di esecuzione, senza che il cliente si preoccupi dell'implementazione concreta ricevuta. BlueThingFactory istanzia BlueThing , RedThingFactory istanzia RedThing , ma il cliente si preoccupa solo che riceva un Thing .

Questo è con fabbriche più sofisticate.

Ma non sono in grado di vedere il vantaggio di utilizzare una fabbrica semplice come nell'esempio che ho descritto. Tutto ciò che fa è spostare new Thing() in un altro posto. Non capisco il vantaggio di questo.

Per favore, spiegami i reali benefici dell'utilizzo di un semplice stabilimento.

    
posta Aviv Cohn 17.05.2014 - 14:41
fonte

6 risposte

7
  • I metodi di produzione potrebbero consentire interfacce con un nome migliore . new Range(0, n - 1) non è leggibile come Range.upto(n) . Nota anche che in alcune lingue (Perl, Python, Ruby), new non è una parola chiave ma al massimo una convenzione, quindi è preferibile selezionare un nome di metodo appropriato per il costruttore.

  • Le fabbriche (non singleton o costrutti simili tramite metodi statici) semplificano i test e aumentano la flessibilità. Qui è importante bilanciare YAGNI con pianificazione della modifica (e un contenitore DI è molto più semplice da modificare rispetto alla ricompilazione di tutto dopo aver eseguito s/ClassA/ClassB/ sull'intera sorgente).

  • In alcune lingue, il costruttore non può essere parte di un'interfaccia . Qui, abbiamo bisogno di offrire un metodo che avvolge il costruttore. Questo è importante in alcuni casi come un modello di Monade.

  • I metodi di produzione sono un ottimo modo per applicare la programmazione a un'interfaccia , non un'implementazione, poiché l'utente non ha alcun controllo sulla classe specifica che viene restituita.

risposta data 17.05.2014 - 15:09
fonte
6

fact.createThing(args) consente alla factory di restituire una sottoclasse di Thing (dipendenza da iniezione) o un oggetto raggruppato di Thing.

Entrambi hanno i loro vantaggi; l'opzione sottoclasse consente a Thing di essere astratto o un'interfaccia e consente di essere deriso nei test.

L'opzione pool è utile solo quando Thing utilizza una risorsa limitata o è costoso da allocare.

    
risposta data 17.05.2014 - 15:01
fonte
3

Non ce n'è. In realtà, usare la fabbrica in questo semplice caso potrebbe essere controproducente, perché stai aggiungendo una nuova classe con zero benefici. E ogni tipo di argomento che dice che c'è un vantaggio si sposta da questa semplice chiamata di metodo a una fabbrica più sofisticata. Come hai detto, hanno dei vantaggi.

Quindi, secondo YAGNI, se non ti aspetti che la classe di istanza venga fornita dall'esterno o richieda qualche tipo di logica di creazione speciale, non è assolutamente necessario creare una classe factory specifica. In realtà, i costruttori sono pensati per essere fabbriche così semplici.

Credo che l'uso massiccio di fabbriche e modelli di progettazione come questo sia un grande esempio di programmazione settoriale del carico. Dove le persone usano i modelli a casaccio senza sapere realmente quale tipo di problemi i modelli sono destinati a risolvere. Ed è uno dei motivi per cui Java è considerato gonfio.

    
risposta data 18.05.2014 - 09:24
fonte
1

Supponiamo che in seguito ti venga in mente una versione 2 di Thing della classe, ad es. Cosa2. Invece di trovare tutte le "nuove cose" e cambiarle tutte in "nuova cosa2", devi solo cambiare un'istruzione all'interno della fabbrica.

Trovo utile con unit test dove cambio la factory per creare la classe mock (ad esempio "new ThingMock"). Il resto del codice rimane lo stesso.

Forse il contenuto della classe Thing viene letto da internet, quindi il metodo "createThing" può fare un download di un oggetto, oppure la nuova classe può essere caricata da un file, avere contenuti casuali, ecc. In questi casi ci sono molti istruzioni all'interno di createThing e sarebbe difficile apportare tale modifica in tutte le istruzioni "nuova cosa".

    
risposta data 17.05.2014 - 14:59
fonte
1

È "migliore", perché ottieni meno dipendenze.

Una delle cose peggiori nello sviluppo del software sono le dipendenze. Le dipendenze rendono molto difficile modificare il software in seguito. Le dipendenze circolari sono anche peggiori.

Quando dici new Thing() , il tuo codice cliente ora fa affidamento sulla classe di cose concreta. Hai creato una strong dipendenza dal tuo codice cliente in una classe molto specifica, Thing .

Quando dici factory.CreateThing() puoi utilizzare qualsiasi classe che fornisce l'interfaccia richiesta e il tuo codice cliente non ha più una dipendenza da una classe concreta.

Ma scegliere quale è "migliore" è un equilibrio di molti fattori e l'introduzione di fabbriche per tutti i concetti del dominio aggiungerà altre complessità al codice e richiederà più tempo per scrivere. Quindi può essere migliore da un punto di vista architettonico, ma non necessariamente migliore da un punto di vista pratico, se la complessità dell'applicazione non trae vantaggi significativi da questa astrazione.

    
risposta data 18.05.2014 - 09:45
fonte
1

Se si tratta di un oggetto semplice con una fabbrica semplice (come il tuo esempio), allora non c'è alcun vantaggio. Sarei molto dispiaciuto di metterlo in una fabbrica (per caso contro YAGNI).

Le cose diventano interessanti mentre il sistema cambia e quell'oggetto semplice inizia a raccogliere comportamenti.

Aggiunta di un argomento

Quando aggiungo un argomento al costruttore, sono di fronte a una decisione. Sovraccaricare il costruttore o modificare tutto il codice client per utilizzare questo nuovo argomento.

  • Se è facoltativo, sovraccaricare il costruttore è naturale.

Ma cosa succede se questo è ora un campo obbligatorio?

  • Se l'oggetto in questione è un oggetto valore, probabilmente ne ho creati decine nei miei test. A questo punto creo quasi sempre un factory di prova per quell'oggetto value, così posso continuare ad aggiungere dati di test predefiniti senza modificare molti test. Questo può essere applicato nel caso generale, ma è anche un odore se hai bisogno di una fabbrica per ogni tipo di test, cosa stai testando veramente?

  • Se non è un oggetto valore, è probabile che aggiunga l'argomento ovunque, spero di non averne fatto troppi.

Aggiunta di un secondo / terzo argomento

A questo punto dovrei avere una buona idea di cosa sia questo oggetto.

  • Se si tratta di un oggetto valore, allora mi limito a rotolare con la strategia precedente. Non creerei necessariamente una "fabbrica", ma mi sembra di aver bisogno di un posto centralizzato per creare questi valori. Questo è spesso il caso quando si traducono i dati da un formato in quello di cui il mio sistema ha bisogno.

  • Se è qualcos'altro, quanti posti posso creare questo codice? Se solo un posto, basta aggiungere l'argomento in atto. È naturale, è dove hai scoperto che avevi bisogno di un'altra cosa.

  • Se è in molti luoghi, ha senso usare questo oggetto in tutti questi posti? Se è qualcosa come un logger, allora sì. È ora di costruire una fabbrica.

  • Se si tratta di qualcosa di specifico del dominio senza preoccupazioni trasversali, allora ho bisogno di dare un'occhiata. Perché questa cosa è utile in così tanti posti? Sta facendo troppo? Suddividilo nelle preoccupazioni per le quali ogni dipendenza è necessaria.

  • Se è strettamente focalizzato ma ha bisogno di molte cose per fare il suo lavoro, allora una fabbrica sembra appropriata. A volte è una cosa come le regole di validazione. Puoi lavorare per centralizzare ciò, ma una difesa stratificata ha spesso senso. Un altro angolo che puoi guardare è se tutti gli argomenti che vengono passati appartengono forse, come in ParameterObject o qualcosa di più appropriato.

Miscelazione in alcuni comportamenti in fase di costruzione

Hai bisogno di un pool di oggetti? Condivisione di una connessione? Hai bisogno di un singleton? Hai bisogno di aggiungere debug / logging? Questo è l'adattamento naturale per le fabbriche.

Creazione di oggetti specializzati con argomenti forniti

La mia preferenza personale per le fabbriche è di creare nomi espressivi. Ciò è particolarmente utile quando si segue uno stile di tipo di iniezione dipendente.

StormMaker.createThunderStorm()  // new Storm(80, 20, 360000, Thunder, Lightning)
StormMaker.createTornado()       // new Storm(70, 100, 60000, Lightning)
StormMaker.createBlizzard()      // new Storm(30, 30, 43200000)
    
risposta data 18.05.2014 - 13:52
fonte

Leggi altre domande sui tag