In che modo il modello di fabbrica separa la dipendenza delle classi?

0

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?

    
posta Max 18.04.2017 - 23:54
fonte

4 risposte

1

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).

    
risposta data 19.04.2017 - 00:11
fonte
1

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:

  1. Hai un sottotipo di ShapeFactory corrispondente a Square .
  2. Registra un'istanza di 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.
   }
}
    
risposta data 19.04.2017 - 00:30
fonte
0

Per aggiungere alle altre risposte: l'accoppiamento che è stato eliminato in modo specifico:

  1. Riferimenti diretti dal codice cliente a (potenzialmente molti diversi) classi concrete tramite invocazioni del costruttore, r.g. new operazioni.

  2. Lo schema generale di selezione della giusta classe (secondaria) di calcestruzzo e altrimenti inizializzazione dell'oggetto.

  3. 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.

    
risposta data 19.04.2017 - 01:58
fonte
0

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.

    
risposta data 19.04.2017 - 11:28
fonte