Capisco che il codice principale utilizza la fabbrica per restituire un puntatore astratto dell'oggetto desiderato, ma non modifica l'ereditabilità delle classi.
Puoi spiegare in quale contesto il modello factory riduce l'accoppiamento?
Usando una fabbrica, il codice chiamante può ignorare quale sia l'implementazione concreta. Che disaccoppia il codice chiamante dalle implementazioni di un'interfaccia (o da qualsiasi astrazione che la fabbrica restituisce). Il codice chiamante può anche ignorare quali sono le regole per la creazione dell'implementazione. Che separa il tuo codice chiamante da quella logica (e consente di riutilizzarlo).
Supponiamo che tu abbia una classe base Shape
.
class Shape
{
virtual ~Shape() = 0;
virtual double getArea() const = 0;
};
E una funzione per costruire un oggetto Shape
del tipo specificato:
Shape* constructShape(std::string const& shapeType);
Nel codice cliente, si desidera utilizzare:
Shape* shape = constructShape("Square");
if ( shape != nullptr )
{
// Use shape
}
else
{
// Deal with the error.
}
Puoi implementare constructShape
come:
Shape* constructShape(std::string const& shapeType)
{
if ( shapeType == "Square" )
{
return new Square;
}
else if ( shapeType == "Circle" )
{
return new Circle;
}
// ....
// Similar code for other known shape types.
// ...
else
{
// Unknown shape type
return nullptr;
}
}
Qui, constructShape
è strongmente accoppiato con i tipi di Shape
s di cui l'applicazione è a conoscenza. Se un nuovo sottotipo di Shape
viene aggiunto all'applicazione, constructShape
deve essere aggiornato per supportare la sua costruzione.
Ora, cambia quella per usare uno schema di fabbrica.
ShapeFactory.h:
// Add the necessary #include lines
// and forward declaration lines.
class ShapeFactory
{
public:
static void registerFactory(std::string const& shapeType,
ShapeFactory* factory);
static Shape* constructObject(std::string const& shapeType);
virtual Shape* build() = 0;
};
constructShape
può essere implementato usando:
Shape* constructShape(std::string const& shapeType)
{
return ShapeFactor::constructObject(shapeType);
}
ShapeFactory.cpp:
// Add the necessary #include lines
typedef std::map<std::string, ShapeFactory*> ShapeFactoryMap;
static ShapeFactoryMap& getShapeFactoryMap()
{
static ShapeFactoryMap theMap;
return theMap;
}
void ShapeFactory::registerFactory(std::string const& shapeType,
ShapeFactory* factory)
{
getShapeFactoryMap()[shapeType] = factory;
}
Shape* ShapeFactory::constructObject(std::string const& shapeType)
{
ShapeFactory* factory = getShapeFactoryMap()[shapeType];
if ( factory == nullptr )
{
return nullptr;
}
else
{
return factory->build();
}
}
Quando Square
viene aggiunto all'applicazione, dovrai assicurarti che:
ShapeFactory
corrispondente a Square
. ShapceFactory
con la classe base. class SquareFactory : public ShapeFactory
{
Shape* build() { return new Square; }
};
ShapeFactor::registerFactory("Square", new SquareFactory);
Con questo, constructShape
e ShapeFactory
conoscono solo la classe base Shape
. Funzioneranno invariati per qualsiasi nuovo sottotipo di Shape
a condizione che per il nuovo sottotipo vengano seguiti i passaggi sopra descritti per Square
.
Aggiornamento, in risposta al commento di OP
main
può essere semplice come:
int main()
{
std::cout << "Enter type of object to construct: ";
std::cin >> shapeType;
Shape* shape = constructShape(shapeType);
if ( shape != nullptr )
{
// Use shape
}
else
{
// Deal with the error.
}
}
Per aggiungere alle altre risposte: l'accoppiamento che è stato eliminato in modo specifico:
Riferimenti diretti dal codice cliente a (potenzialmente molti diversi) classi concrete tramite invocazioni del costruttore, r.g. new
operazioni.
Lo schema generale di selezione della giusta classe (secondaria) di calcestruzzo e altrimenti inizializzazione dell'oggetto.
Anche scegliendo se creare una nuova istanza di oggetto ogni volta, anziché restituire e riutilizzare un'istanza creata in precedenza.
Estrapolando tutti questi dettagli di creazione, il cliente si preoccupa principalmente di usare l'interfaccia dell'oggetto restituito. I nomi delle classi concrete, il numero se esse, anche se la stessa istanza dell'oggetto è riutilizzabile, sono astratte dal client.
L'accoppiamento non riguarda ciò che accade a livello di macchina, si tratta di quanto deve essere preoccupato un umano che legge il codice.
Se sei corretto, un metodo factory che restituisce un'interfaccia potrebbe restituire esattamente lo stesso oggetto che usa direttamente il costruttore. Ma incapsulando la costruzione, il cliente potrebbe essere protetto da molti dettagli di implementazione.
Supponiamo che tu abbia un'interfaccia ILogger
che ha alcune implementazioni concrete come FileLogger
e DatabaseLogger
. L'interfaccia ILogger
astrae le differenze. Quindi, quando utilizzi l'interfaccia ILogger
, sei disaccoppiato dalla conoscenza di come viene effettivamente implementato il logger, anche l'oggetto reale che usi è un'istanza di una delle implementazioni concrete.
Leggi altre domande sui tag design-patterns object-oriented-design factory-method abstract-factory