Abstract Factory Design Pattern

A

Introduzione

Dopo aver parlato di Factory Method, Singleton e Build Pattern trattiamo un altro design pattern tra quelli classificati come creazionali dalla GoF, ovvero Abstract Factory (aka Kit).

L’intento di questo pattern è il seguente:

Fornire un’interfaccia per creare famiglie di oggetti correlati o dipendenti senza specificare le loro classi concrete limitandone quindi l’uso diretto.

Motivazioni

In alcuni casi può essere necessario utilizzare un sistema in diversi contesti con il conseguente utilizzo di classi differenti, ma della stessa “famiglia”.
L’utilizzo diretto delle classi concrete crea un accoppiamento forte e limita la portabilità del sistema stesso.
Questo porta alla necessità di rendere il sistema indipendente dalla modalità di creazione delle classi concrete, facendo si che soltanto le interfacce siano note e non le implementazioni.
Il pattern Abstract Factory consente di rendere tra loro interscambiabili le diverse implementazioni che soddisfano una determinata interfaccia, senza che il contesto d’uso dell’istanza debba essere modificato al variare dell’implementazione scelta.

Struttura

Prendiamo come riferimento della struttura il class diagram definito dalla GoF.

Abstract Factory Class Diagram
Abstract Factory Class Diagram

Partecipanti

I partecipanti di questo pattern sono (tra parentesi gli oggetti utilizzati nell’esempio):

AbstractFactory (AbstractFactory)
Definisce l’interfaccia di riferimento per gli oggetti che creano le istanze.

ConcreteFactory (ShapeFactory)
Implementa in modo concreto l’interfaccia definita da AbstractFactory e crea effettivamente una tipologia specifica di oggetti appartenenti ad una famiglia.

AbstractProduct (Shape)
Definisce l’interfaccia di riferimento per una famiglia di oggetti da creare tramite il factory corrispondente.

ConcreteProduct (Circle, Square e Rectangle)
Implementa in modo concreto l’oggetto appartenente alla famiglia per cui vale l’interfaccia AbstractProduct e che viene creato dall’oggetto factory corrispondente.

Client (Program)
Utilizza unicamente le classi astratte del factory e dell’oggetto da creare, senza conoscerne gli aspetti implementativi. L’annullamento dell’accoppiamento tra il client e gli oggetti concreti è ottenuto tramite l’inversione delle dipendenze, uno dei principi base dell’Object Oriented Design (OOD).

Implementazione

Consideriamo a titolo di esempio la famiglia di oggetti Shape. Vogliamo far in modo che sia possibile istanziare oggetti di diversa “forma” senza utilizzare direttamente le rispettive classi concrete, ma utilizzando l’AbstractFactory.

Vediamo di seguito l’implementazione in Java dei vari elementi discussi sopra.

public interface Shape {
   void draw();
}
public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Drow a Rectangle");
   }
}
public class Square implements Shape {

   @Override
   public void draw() {
      System.out.println("Drow a Square");
   }
}
public class RoundedRectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Drow a RoundedRectangle");
   }
}
public class RoundedSquare implements Shape { 
    @Override public void draw() { 
        System.out.println("Drow a RoundedSquare"); 
    } 
}
public abstract class AbstractFactory {
   abstract Shape getShape(String shape) ;
}
public class ShapeFactory extends AbstractFactory {
  
   @Override
   public Shape getShape(String shapeType){
   
      if(shapeType == null){
         return null;
      }		
      
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
         
      }else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new Square();
      }
      
      return null;
   }
}
public class RoundedShapeFactory extends AbstractFactory {
  
   @Override
   public Shape getShape(String shapeType){
   
      if(shapeType == null){
         return null;
      }    
      
      if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new RoundedRectangle();
         
      }else if(shapeType.equalsIgnoreCase("SQUARE")){
         return new RoundedSquare();
      }
      
      return null;
   }
public class FactoryProducer {
   public static AbstractFactory getFactory(boolean rounded){   
      if(rounded){
         return new RoundedShapeFactory();         
      }else{
         return new ShapeFactory();
      }
   }
}
public class Client {
  public static void main(String[] args) {
    AbstractFactory shapeFactory = FactoryProducer.getFactory(false);
    Shape shape1 = shapeFactory.getShape("RECTANGLE");
    shape1.draw();
    Shape shape2 = shapeFactory.getShape("SQUARE");
    shape2.draw();

    AbstractFactory shapeFactory1 = FactoryProducer.getFactory(true);
    Shape shape3 = shapeFactory1.getShape("RECTANGLE");
    shape3.draw();
    Shape shape4 = shapeFactory1.getShape("SQUARE");
    shape4.draw();
  }
}

L’output del codice sopra sarà il seguente:

Drow a Rectangle
Drow a Square
Drow a RoundedRectangle 
Drow a RoundedSquare

Conseguenze

Facendo un riepilogo riportiamo quelli che sono vantaggi e svantaggi di questo pattern.

Vantaggi

  • Isolamento delle classi concrete. Aiuta a controllare le classi di oggetti creati da un’applicazione. Poiché un factory incapsula la responsabilità e il processo di creazione di oggetti e isola i client dalle classi concret
  • Facilità lo scambio di oggetti della stessa famiglia. Una classe concreta appare una sola volta nel codice, quando viene istanziata dal factory che è l’unico oggetto ad averne il controllo. Basta quindi modificare la ConcreteFactory.
  • Promuove la consistenza tra oggetti della stessa famiglia. Semplifica la cooperazione tra oggetti della stessa famiglia

Svantaggi

  • Supportare nuovi tipi di prodotto è difficile. Dato che AbstractFactory definisce tutte le varie tipologie di prodotti che è possibile istanziare, aggiungere una famiglia significa modificare l’interfaccia della factory. La modifica si ripercuote a cascata nelle factory concrete e in tutte le sottoclassi, rendendo laboriosa l’operazione.

A proposito di me

Alessio Fiore

Grande appassionato di sviluppo software e linguaggi di programmazione ha una grande conoscenza nel mondo Java e framework come Spring e Hibernate.
Laureato magistrale in Ingegneria Informatica lavora come sviluppatore software in ambito Telco principalmente lato backend, ma coltivando sempre molto interesse su tecnologie frontend.
Ha una grande esperienza su system integration, architetture software e sistemi distribuiti.

Di Alessio Fiore

Gli articoli più letti

Articoli recenti

Commenti recenti