La ragione per cui non vedi i metodi di factory static elencati nel book GoF è perché questo pattern non usa il polimorfismo in alcun modo interessante. Il tuo diagramma suggerisce questo, ma la maggior parte delle lingue non supporta la struttura che mostra. In particolare, un metodo statico non può essere anche virtuale. Non è presente alcun oggetto istanza da inviare. Non è possibile sovrascrivere un metodo statico nel modo in cui è possibile sovrascrivere un metodo di istanza (alcuni linguaggi hanno tuttavia dei metodi di classe, dove è possibile eseguire l'invio sull'oggetto classe). Mentre il libro GoF è solo preoccupato di mostrare modelli orientati agli oggetti, i metodi statici di fabbrica sono ancora uno schema comune in molte lingue.
Che cosa la pagina che hai collegato a descrive sono due modelli distinti.
Per prima cosa abbiamo lo schema comune che anziché esporre un costruttore della nostra classe, forniamo un metodo statico. Molti linguaggi ci consentono di definire più costruttori e risolverli attraverso l'overloading dei metodi, ma i metodi statici possono avere nomi diversi che sono molto più adatti ai programmatori. La pagina collegata utilizza Color
oggetti come esempio. new Color(float, float, float)
potrebbe essere ovvio, ma non può essere sovraccaricato con modelli di colori diversi (RGB vs HSV). I metodi statici possono disambiguare il nome: Color::make_rgb(float, float, float)
contro Color::make_hsv(float, float, float)
. Si tratta di una buona progettazione dell'API, ma non ha nulla a che fare con l'orientamento agli oggetti.
L'utilizzo di metodi di produzione (statici) ci offre anche maggiore libertà nell'implementazione. Potremmo mappare i colori HSV in RGB internamente. Oppure Color
potrebbe essere astratto e utilizzare diverse sottoclassi per ogni spazio colore, senza esporre questa differenza all'utente. Questo dettaglio di implementazione è interamente incapsulato:
public static Color makeRGB(float r, float g, float b) {
return new ColorRGB(r, g, b);
}
public static Color makeHSV(float h, float s, float v) {
return new ColorHSV(h, s, v);
}
Nella mia esperienza, tali metodi rendono il codice meno ambiguo e rendono un'API più accessibile e manutenibile. In C ++, c'è il vantaggio aggiuntivo che i metodi statici possono utilizzare la deduzione degli argomenti del modello, mentre i parametri del modello di classe devono essere forniti esplicitamente in una chiamata del costruttore.
I metodi di produzione hanno un'altra caratteristica interessante: possiamo decidere nel nostro codice quale tipo di calcestruzzo istanziare. Ho usato questo in alcuni parser:
Expression makeBinaryExpression(Expression left, String operator, Expression right) {
if (operator == "+") return new Addition(left, right);
if (operator == "-") return new Subtraction(left, right);
if (operator == "*") return new Multiplication(left, right);
if (operator == "/") return new Division(left, right);
throw new ParseException("Expected +, -, *, /, but got " + operator);
}
Hai ragione che tali metodi statici rappresentano un problema di estensibilità. Come posso aggiungere il modello di colore CMYK alla classe Color
? Non posso, senza modificare la classe Color
, o sostituirla con una classe diversa ma compatibile (cambiando le importazioni, o con i modelli C ++). Se è richiesta l'estensibilità, l'API dovrebbe essere resa virtuale, torneremo su quella in un minuto.
Tuttavia, non tutto il codice deve essere polimorfico. Soprattutto se il comportamento è "puro" e non interagisce con nessuno stato esterno e quando il comportamento non cambia, i metodi non virtuali hanno ancora un posto.
Inoltre, il principio open-closed è rilevante solo quando non è possibile modificare la fonte e rilasciare una nuova versione. Questo è il caso delle API pubblicate in cui l'editore e il consumatore si trovano in organizzazioni diverse. In tal caso, deve esserci un'API chiara e questa interfaccia deve essere attentamente progettata per consentire ai consumatori di adattarla alle loro esigenze, spesso richiedendo un'iniezione di dipendenza e esprimendo coerentemente l'API in termini di interfacce implementabili dall'utente. Ma se il codice viene utilizzato solo all'interno dello stesso progetto, l'OCP non è così rilevante (basandosi su interfacce può ancora rendere più semplice il refactoring, però).
Ora torniamo al polimorfismo. Il libro GoF elenca il modello di metodo di fabbrica e lo associa a un "costruttore virtuale". L'intero punto qui è che il metodo factory è virtuale e sovrascrivibile. Questo ovviamente significa che un metodo factory sulla classe Product
è inutile, dal momento che dovremmo avere un Product
esistente per crearlo. Al contrario, esiste una classe Creator
diversa. Il metodo factory è quindi un esempio del Pattern Method Template che può essere sostituito da sottoclassi di Creator per fornire una sottoclasse Product specifica. Abstract Factory Pattern è un esempio di un creatore con più metodi di fabbrica correlati.
Occasionalmente, i metodi di fabbrica sono anche usati come meccanismo di iniezione delle dipendenze. La classe base definisce alcuni metodi di template per costruire le dipendenze, che possono poi essere cambiate attraverso la sottoclasse. Tuttavia, la mia esperienza dimostra che l'utilizzo di piccoli oggetti strategici per aprire la costruzione delle dipendenze è un approccio più flessibile e semplice piuttosto che richiedere l'ereditarietà dell'intera classe di destinazione.