In che modo un altro linguaggio popolare eviterebbe di utilizzare il modello di fabbrica gestendo una complessità simile a quella di Java / Java EE?


23

Il modello di fabbrica (o almeno l'uso di FactoryFactory..) è il calcio di molte battute, come qui .

Oltre ad avere nomi verbosi e "creativi" come RequestProcessorFactoryFactory.RequestProcessorFactory , c'è qualcosa di fondamentalmente sbagliato nel modello di fabbrica se devi programmare in Java / C ++ e c'è un caso d'uso per Abstract_factory_pattern ?

In che modo un altro linguaggio popolare (ad esempio, Ruby o Scala ) eviterebbe di doverlo utilizzare gestendo una complessità simile?

Il motivo per cui mi chiedo è che vedo principalmente le critiche alle fabbriche menzionate nel contesto dell'ecosistema EE Java / Java , ma non spiegano mai come altri linguaggi / framework le risolvano.



4
Il problema non è l'uso delle fabbriche: è un modello perfettamente preciso. È l'abuso di fabbriche che è particolarmente diffuso nel mondo imprenditoriale di Java.
Codici A Caos

Link scherzoso molto bello. Mi piacerebbe vedere un'interpretazione funzionale del linguaggio di recupero degli strumenti per costruire un portaspezie. Penso che lo porterebbe davvero a casa.
Patrick M,

Risposte:


28

La tua domanda è taggata con "Java", nessuna sorpresa ti stai chiedendo perché il modello Factory viene deriso: Java stesso viene fornito con abusi ben confezionati di quel modello.

Ad esempio, prova a caricare un documento XML da un file ed esegui una query XPath su di esso. Hai bisogno di qualcosa come 10 righe di codice solo per configurare le Fabbriche e i Costruttori:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Mi chiedo se i ragazzi che progettano questa API abbiano mai lavorato come sviluppatori o abbiano semplicemente letto libri e lanciato cose. Capisco che non volevano scrivere da soli un parser e hanno lasciato il compito ad altri, ma rende comunque una brutta implementazione.

Dato che stai chiedendo quale sia l'alternativa, ecco il caricamento del file XML in C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Sospetto che gli sviluppatori Java appena nati vedano la follia di Factory e penso che sia OK farlo - se i geni che hanno costruito Java stessi li hanno usati così tanto.

Factory e qualsiasi altro modello sono strumenti, ognuno adatto per un determinato lavoro. Se li applichi a lavori che non sono adatti per te, sei obbligato ad avere un codice brutto.


1
Solo una nota che la tua alternativa a C # sta usando il più recente LINQ to XML - la più vecchia alternativa DOM sarebbe stata simile a questa: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Nel complesso, LINQ to XML è molto più facile da usare, anche se principalmente in altre aree.) Ed ecco un articolo KB Ho trovato, con un metodo raccomandato da MS: support.microsoft.com/kb/308333
Bob

36

Come spesso accade, le persone fraintendono ciò che sta succedendo (e questo include molte risate).
Non è il modello di fabbrica in sé che è cattivo tanto quanto il modo in cui molte persone (forse anche la maggior parte) lo usano.

E questo senza dubbio deriva dal modo in cui viene insegnata la programmazione (e gli schemi). Agli scolari (che spesso si definiscono "studenti") viene detto di "creare X usando il modello Y" e dopo alcune iterazioni di ciò pensano che sia il modo di affrontare qualsiasi problema di programmazione.
Quindi iniziano ad applicare uno schema specifico che gli piace a scuola contro qualsiasi cosa, sia appropriata che no.

E questo include i professori universitari che scrivono libri sulla progettazione di software, purtroppo.
Il culmine di questo era un sistema che avevo il chiaro dispiacere di dover mantenere, che era stato creato da uno di questi (l'uomo aveva persino diversi libri sul design orientato agli oggetti a suo nome e una posizione di insegnamento nel dipartimento CS di una grande università ).
Si basava su un modello a 3 livelli, con ogni livello stesso un sistema a 3 livelli (devi disaccoppiare ...). Sull'interfaccia tra ogni set di livelli, su entrambi i lati, c'era una fabbrica per produrre gli oggetti per trasferire i dati all'altro livello e una fabbrica per produrre l'oggetto per tradurre l'oggetto ricevuto in uno appartenente al livello ricevente.
Per ogni fabbrica c'era una fabbrica astratta (chissà, la fabbrica potrebbe dover cambiare e quindi non vuoi che il codice chiamante debba cambiare ...).
E tutto quel casino era ovviamente completamente privo di documenti.

Il sistema aveva un database normalizzato in quinta forma normale (non ti scherzo).

Un sistema che era essenzialmente poco più di una rubrica che poteva essere utilizzata per registrare e tenere traccia dei documenti in entrata e in uscita, aveva una base di codice da 100 MB in C ++ e oltre 50 tabelle di database. La stampa di lettere da 500 moduli richiederebbe fino a 72 ore utilizzando una stampante collegata direttamente al computer che esegue il software.

È per questo che le persone deridono i modelli, e in particolare le persone che si concentrano da sole su un singolo modello specifico.


40
L'altra ragione è che alcuni dei modelli di Gang of Four sono solo degli hack davvero dettagliati per cose che possono essere fatte in modo banale in un linguaggio con funzioni di prima classe, che li rende inevitabili anti-pattern. I modelli "Strategia", "Osservatore", "Fabbrica", "Comando" e "Metodo modello" sono trucchi per passare funzioni come argomenti; "Visitatore" è un trucco per eseguire una corrispondenza di pattern su un tipo di somma / variante / unione con tag. Il fatto che alcune persone facciano molto rumore per qualcosa che è letteralmente banale in molte altre lingue è ciò che porta le persone a deridere C ++, Java e linguaggi simili.
Doval,

7
Grazie per questo aneddoto. Puoi anche approfondire un po 'le "quali sono le alternative?" parte della domanda quindi non diventiamo così anche noi?
Philipp

1
@Doval Puoi dirmi come si restituisce valore dal comando eseguito o chiamato strategia quando si può solo passare la funzione? E se dici chiusure, tieni presente che è esattamente la stessa cosa della creazione di una classe, tranne che più esplicita.
Euforico

2
@Euforico Non vedo il problema, puoi approfondire? In ogni caso, non sono esattamente la stessa cosa. Si potrebbe anche dire che un oggetto con un singolo campo è esattamente lo stesso di un puntatore / riferimento a una variabile o che un intero è esattamente lo stesso di un enum (reale). Ci sono differenze nella pratica: non ha senso confrontare le funzioni; Devo ancora vedere una sintassi concisa per le classi anonime; e tutte le funzioni con lo stesso argomento e i tipi restituiti hanno lo stesso tipo (a differenza delle classi / interfacce con nomi diversi, che sono tipi diversi anche quando sono identici).
Doval

2
@Euphoric Correct. Sarebbe considerato un cattivo stile funzionale se c'è un modo per fare il lavoro senza effetti collaterali. Una semplice alternativa sarebbe quella di utilizzare un tipo di somma / unione taggata per restituire uno di N tipi di valori. Indipendentemente da ciò, supponiamo che non esista un modo pratico; non è la stessa di una classe. In realtà è un'interfaccia (nel senso di OOP.) Non è difficile capire se si considera che qualsiasi coppia di funzioni con le stesse firme sarebbe intercambiabile, mentre due classi non sono mai intercambiabili anche se hanno gli stessi identici contenuti.
Doval,

17

Le fabbriche presentano molti vantaggi che consentono di progettare applicazioni eleganti in alcune situazioni. Uno è che puoi impostare le proprietà degli oggetti che in seguito desideri creare in un unico posto creando una fabbrica, e poi consegnare quella fabbrica. Ma spesso in realtà non è necessario farlo. In tal caso, l'utilizzo di una Factory aggiunge ulteriore complessità senza in realtà darti nulla in cambio. Prendiamo questa fabbrica, ad esempio:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Un'alternativa al modello Factory è il modello Builder molto simile. La differenza principale è che le proprietà degli oggetti creati da una Factory vengono impostate quando la Factory viene inizializzata, mentre un Builder viene inizializzato con uno stato predefinito e tutte le proprietà vengono impostate in seguito.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Ma quando l'eccessiva ingegnerizzazione è il tuo problema, sostituire una fabbrica con un costruttore non è probabilmente un grande miglioramento.

La sostituzione più semplice per entrambi i pattern è ovviamente quella di creare istanze di oggetti con un semplice costruttore con l' newoperatore:

Widget widget = new ColoredWidget(COLOR_RED);

I costruttori, tuttavia, presentano uno svantaggio cruciale nella maggior parte dei linguaggi orientati agli oggetti: devono restituire un oggetto di quella classe esatta e non possono restituire un sottotipo.

Quando è necessario scegliere il sottotipo in fase di esecuzione ma non si desidera ricorrere alla creazione di una classe Builder o Factory completamente nuova, è possibile utilizzare invece un metodo factory. Questo è un metodo statico di una classe che restituisce una nuova istanza di quella classe o una delle sue sottoclassi. Una fabbrica che non mantiene alcuno stato interno può spesso essere sostituita con un tale metodo di fabbrica:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Una nuova funzionalità di Java 8 sono riferimenti ai metodi che consentono di passare i metodi in giro, proprio come si farebbe con una fabbrica senza stato. Convenientemente, tutto ciò che accetta un riferimento al metodo accetta anche qualsiasi oggetto che implementa la stessa interfaccia funzionale, che può anche essere una Fabbrica a pieno titolo con stato interno, quindi puoi facilmente introdurre fabbriche in seguito, quando vedi un motivo per farlo.


2
Una fabbrica può spesso essere configurata anche dopo la creazione. La principale differenza, rispetto a un costruttore, è che un costruttore viene in genere utilizzato per creare un'unica istanza complessa, mentre le fabbriche vengono utilizzate per creare molte istanze simili.
Cefalopode

1
grazie (+1), ma la risposta non spiega come altri PL lo risolverebbero, tuttavia grazie per aver indicato i riferimenti al metodo Java 8.
senseiwu,

1
Ho spesso pensato che avrebbe senso in un framework orientato agli oggetti limitare la costruzione dell'oggetto reale al tipo in questione, ed foo = new Bar(23);essere equivalente a foo = Bar._createInstance(23);. Un oggetto dovrebbe essere in grado di interrogare in modo affidabile il proprio tipo usando un getRealType()metodo privato , ma gli oggetti dovrebbero essere in grado di designare un supertipo a cui restituire le chiamate esterne getType(). Al codice esterno non dovrebbe importare se una chiamata new String("A")restituisce effettivamente un'istanza di String[anziché un'istanza di SingleCharacterString].
supercat

1
Uso molte delle fabbriche di metodi statici quando devo scegliere una sottoclasse in base al contesto, ma le differenze dovrebbero essere opache per il chiamante. Ad esempio, ho una serie di classi di immagini che contengono tutte, draw(OutputStream out)ma generano HTML leggermente diverso, la fabbrica del metodo statico crea la classe giusta per la situazione e quindi il chiamante può semplicemente usare il metodo di disegno.
Michael Shopsin

1
@supercat potresti essere interessato a dare un'occhiata a goal-c. La costruzione di oggetti consiste in semplici chiamate di metodi, di solito [[SomeClass alloc] init]. È possibile restituire un'istanza di classe completamente diversa o un altro oggetto (come un valore memorizzato nella cache)
axelarge

3

Fabbriche di un tipo o di un altro si trovano praticamente in qualsiasi linguaggio orientato agli oggetti in circostanze appropriate. A volte hai semplicemente bisogno di un modo per selezionare quale tipo di oggetto creare in base a un semplice parametro come una stringa.

Alcune persone lo spingono troppo in là e provano a progettare il loro codice per non dover mai chiamare un costruttore se non all'interno di una fabbrica. Le cose iniziano a diventare ridicole quando si hanno fabbriche di fabbrica.

Ciò che mi ha colpito quando ho imparato Scala, e non conosco Ruby, ma credo che sia più o meno allo stesso modo, è che il linguaggio è abbastanza espressivo che i programmatori non stanno sempre cercando di spingere il lavoro "idraulico" su file di configurazione esterni . Piuttosto che essere tentati di creare fabbriche di fabbrica per collegare le tue classi in diverse configurazioni, in Scala usi oggetti con tratti misti. È anche relativamente facile creare semplici DSL in linguaggio per attività che sono spesso troppo ingegnerizzate in Java.

Inoltre, come hanno sottolineato altri commenti e risposte, chiusure e funzioni di prima classe evitano la necessità di molti schemi. Per questo motivo, credo che molti degli anti-pattern inizieranno a scomparire quando C ++ 11 e Java 8 ottengono un'adozione diffusa.


grazie (+1). La tua risposta spiega alcuni dei miei dubbi su come Scala avrebbe evitato. Onestamente, non pensi che una volta (se) Scala diventerà almeno la metà popolare di Java a livello aziendale, si apriranno eserciti di framework, modelli, server di app a pagamento ecc.?
senseiwu,

Penso che se Scala dovesse superare Java, sarà perché la gente preferisce il "modo Scala" del software di architettura. Ciò che mi sembra più probabile è che Java continui ad adottare più funzioni che spingono le persone a passare a Scala, come i generici di Java 5 e i lambdas e i flussi di Java 8, che si spera che alla fine si tradurrà in programmatori che abbandonano gli anti-pattern che sono sorti quando quelli le funzionalità non erano disponibili. Ci saranno sempre aderenti FactoryFactory, non importa quanto le lingue siano buone. Ovviamente ad alcune persone piacciono quelle architetture, o non sarebbero così comuni.
Karl Bielefeldt,

2

In generale, vi è questa tendenza secondo cui i programmi Java sono eccessivamente ingegnerizzati [citazione necessaria]. Avere molte fabbriche è uno dei sintomi più comuni dell'ingegneria eccessiva; ecco perché la gente si prende gioco di quelli.

In particolare, il problema che Java ha con le fabbriche è che in Java a) i costruttori non sono funzioni eb) le funzioni non sono cittadini di prima classe.

Immagina di poter scrivere qualcosa del genere (lascia che Functionsia un'interfaccia ben nota di JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Vedi, nessuna interfaccia o classe di fabbrica. Molti linguaggi dinamici hanno funzioni di prima classe. Purtroppo, ciò non è possibile con Java 7 o versioni precedenti (l'implementazione della stessa con le fabbriche viene lasciata come esercizio al lettore).

Quindi, l'alternativa non è tanto "usare un modello diverso", ma di più "usare un linguaggio con un modello di oggetti migliore" o "non progettare eccessivamente problemi semplici".


2
questo non tenta nemmeno di rispondere alla domanda posta: "quali sono le alternative?"
moscerino del

1
Leggi oltre il secondo paragrafo e arriverai alla parte "come lo fanno le altre lingue". (PS: anche l'altra risposta non mostra alternative)
Cefalopode

4
@gnat Non sta indirettamente dicendo che l'alternativa è usare funzioni di prima classe? Se quello che vuoi è una scatola nera in grado di creare oggetti, le tue opzioni sono una fabbrica o una funzione e una fabbrica è solo una funzione mascherata.
Doval,

3
"In molti linguaggi dinamici" è un po 'fuorviante, poiché ciò che è effettivamente necessario è un linguaggio con funzioni di prima classe. "Dinamico" è ortogonale a questo.
Andres F.

Vero, ma è una proprietà che si trova spesso in linguaggi dinamici. Ho menzionato la necessità di una funzione di prima classe all'inizio della mia risposta.
Cefalopode
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.