Un esempio degno di attenzione riguarda le raccolte Java. La gerarchia di tipi al di sotto di Collection<T>
ha alcune subinterfacce e tipi concreti. Ogni struttura di dati presenta vantaggi e svantaggi. Talvolta hanno proprietà aggiuntive al di sopra e al di là delle basi di una particolare struttura di dati come la sicurezza del thread o il mantenimento dell'ordine di inserimento in una struttura altrimenti confusa (ad esempio, alcune mappe e insiemi).
Per aiutare con questo, ci sono diverse fabbriche integrate nel JFC. La classe Collections
può racchiudere una raccolta esistente in una che non è modificabile (la raccolta stessa è immutabile, gli elementi in essa contenuti possono o non possono essere). Può racchiudere una raccolta in un wrapper sincronizzato, offrendo una sicurezza di base per i thread (potrebbe non funzionare bene come le raccolte specializzate nel pacchetto di concorrenza). Può racchiudere un singolo elemento in una raccolta singleton (non nel modello singleton, solo in una raccolta con un elemento).
Questi sono tutti metodi di fabbrica: si chiama un metodo che restituisce un oggetto piuttosto che chiamare direttamente un costruttore. Sono utili perché consentono di adattare una raccolta che potrebbe essere creata al di fuori del proprio controllo (ad esempio in un framework o in una libreria) con vincoli aggiuntivi (ad es. Mutabilità o concorrenza) senza creare esplicitamente un nuovo oggetto.
Guava fa un ulteriore passo avanti con le sue utility di raccolta . Contiene molti altri metodi di fabbrica per creare nuove collezioni di vario tipo con varie proprietà. La costruzione può essere più concisa ed espressiva in questo modo. A livello concettuale non c'è altro da spiegare oltre la classeCollections
di JFC%, ma noterò che Google ha compiuto un bel po 'di sforzi per costruire queste fabbriche perché aggiungono valore .
Questo è solo un esempio di dove le fabbriche possono essere utili, ma dovrebbero sempre essere usate?
Ci sono due ragioni principali per l'utilizzo di una fabbrica:
-
La costruzione è complessa. I costruttori dovrebbero essere semplici: hanno bisogno di dati o elaborazione aggiuntivi? Ad esempio, il caricamento dei dati da un file o da un database non dovrebbe essere eseguito in un costruttore, ma potrebbe essere necessario per creare un oggetto. Ha più senso inserire quell'elaborazione extra in un metodo factory.
-
L'implementazione deve variare a prescindere da dove vengono creati gli oggetti. Potresti voler utilizzare un'implementazione diversa in un momento futuro? Potrebbe essere variabile attraverso un'architettura di plugin? Potresti inserire una simulazione per testare?
Molte classi API non superano questi test. Ad esempio, una stringa avrà sempre la stessa implementazione e non richiede molta elaborazione aggiuntiva. L'API di base della maggior parte delle lingue / runtime deve essere abbastanza semplice per due motivi:
-
Sta risolvendo semplici problemi, ad es. "rappresenta una sequenza di caratteri in una stringa."
-
Deve essere applicabile a una vasta gamma di problemi e altamente riutilizzabile, che è in contrasto con una classe altamente specializzata.
Questa semplicità è in contrasto con i requisiti per l'utilizzo di una fabbrica. Quando un tipo è abbastanza semplice che un costruttore no-arg o one-arg è tutto ciò che è sempre necessario, e l'implementazione sarà sempre la stessa, non ha senso usare una fabbrica.
Nota che sto parlando dell'API core qui (stringhe, raccolte, flussi), non di cose come database, GUI o altre librerie più complesse.