Perché questo ShapeFactory utilizza istruzioni condizionali per determinare quale oggetto creare un'istanza. Non dobbiamo modificare ShapeFactory se vogliamo aggiungere altre classi in futuro? Perché questo non viola il principio aperto chiuso?
Perché questo ShapeFactory utilizza istruzioni condizionali per determinare quale oggetto creare un'istanza. Non dobbiamo modificare ShapeFactory se vogliamo aggiungere altre classi in futuro? Perché questo non viola il principio aperto chiuso?
Risposte:
La saggezza convenzionale orientata agli oggetti è di evitare if
affermazioni e sostituirle con invio dinamico di metodi scavalcati in sottoclassi di una classe astratta. Fin qui tutto bene.
Ma il punto del modello di fabbrica è di sollevarti dal dover conoscere le singole sottoclassi e lavorare solo con la superclasse astratta . L'idea è che la fabbrica sappia meglio di te quale classe specifica creare un'istanza, e starai meglio lavorando solo con i metodi pubblicati dalla super classe. Questo è spesso vero e un modello prezioso.
Pertanto, non è possibile che la scrittura di una classe factory possa rinunciare alle if
dichiarazioni. Sposterebbe l'onere della scelta di una classe specifica per il chiamante, che è esattamente ciò che il modello dovrebbe evitare. Non tutti i principi sono assoluti (in realtà, nessun principio è assoluto) e se si utilizza questo modello, si presuppone che il beneficio da esso sia maggiore del vantaggio di non utilizzare un if
.
if
s. Vedi la risposta di @ BЈовић per un semplice esempio di come raggiungere questo obiettivo. Downvoted.
L'esempio utilizza probabilmente un'istruzione condizionale perché è la più semplice. Un'implementazione più complessa potrebbe usare una mappa o una configurazione o (se vuoi essere davvero fantasioso) un qualche tipo di registro in cui le classi possono registrarsi. Tuttavia non c'è nulla di sbagliato nell'usare un condizionale se il numero di classi è piccolo e cambia di rado.
L'estensione del condizionale per aggiungere il supporto per una nuova sottoclasse in futuro sarebbe in effetti una violazione rigorosa del principio aperto / chiuso. La soluzione "corretta" sarebbe quella di creare un nuovo stabilimento con la stessa interfaccia. Detto questo, l'adesione al principio O / C dovrebbe sempre essere valutata rispetto ad altri principi di progettazione come KISS e YAGNI.
Detto questo, il codice visualizzato è chiaramente un codice di esempio progettato per mostrare il concetto di fabbrica e nient'altro. Ad esempio, è davvero un cattivo stile restituire null come nell'esempio, ma una gestione degli errori più elaborata oscurerebbe semplicemente il punto. Il codice di esempio non è il codice di qualità della produzione, non ci si dovrebbe aspettare che sia.
Il modello stesso non viola il principio aperto / chiuso (OCP). Tuttavia, violiamo l'OCP quando utilizziamo il modello in modo errato.
La semplice risposta a questa domanda è la seguente:
Nell'esempio fornito, la funzionalità di base supporta tre forme: Cerchio, Rettangolo e Quadrato. Supponiamo che tu debba supportare Triangle, Pentagon ed Hexagon in futuro. Per fare questo SENZA violare OCP, è necessario creare un factory aggiuntivo per supportare le nuove forme (chiamiamolo così AdvancedShapeFactory
) e quindi utilizzare AbstractFactory per decidere quale factory è necessario creare per creare le forme necessarie.
Se stai parlando del modello Abstract Factory, il processo decisionale spesso non è nella Factory stessa ma nel codice dell'applicazione. È quel codice che sceglie quale factory concreta istanziare e passare al codice client che utilizzerà gli oggetti prodotti dalla Factory. Vedi la fine dell'esempio Java qui: https://en.wikipedia.org/wiki/Abstract_factory_pattern
Il processo decisionale non implica necessariamente if
dichiarazioni. Potrebbe leggere il tipo Factory concreto da un file di configurazione, derivarlo da una struttura della mappa, ecc.
Se pensi a Open-Close a livello di classe con questa fabbrica stai creando un'altra classe nel tuo sistema Open-Close, ad esempio se hai un'altra classe che prende una Shape e calcola l'area (esempio tipico) questa classe è OpenClose perché può calcolare l'area per nuovi tipi di forme senza modifiche. Quindi hai un'altra classe che disegna la forma, un'altra classe che prende N forme e restituisce quella più grande e puoi pensare in generale che le altre classi nel tuo sistema che si occupano di forme siano Open-Close (almeno sulle forme). Dal punto di vista del design, la fabbrica consente al resto del sistema di essere Open-Close e, naturalmente, di fabbrica, NON È Open-Close.
Ovviamente puoi anche aprire e chiudere questa fabbrica, tramite una sorta di caricamento dinamico e l'intero sistema può essere Open-Close (ad esempio puoi aggiungere nuove forme facendo cadere un vaso nel percorso di classe). È necessario valutare se questa ulteriore complessità vale a seconda del sistema che si sta costruendo, non tutti i sistemi richiedono funzionalità collegabili e non tutto il sistema deve essere completamente Open-Close.
Il principio aperto-chiuso, come il principio di sostituzione di Liskov, si applica agli alberi di classe, alle gerarchie ereditarie. Nel tuo esempio, la classe factory non si trova nell'albero genealogico delle classi che istanzia, quindi non può violare queste regole. Vi sarebbe una violazione se GetShape (o più appropriatamente, CreateShape) fosse implementato nella classe base Shape.
Tutto dipende da come lo implementi. È possibile utilizzare std::map
per contenere i puntatori a funzioni per la creazione di oggetti. Quindi il principio di apertura / chiusura non viene violato. O interruttore / caso.
Ad ogni modo, se non ti piace il modello di fabbrica, puoi sempre usare l'iniezione delle dipendenze.