Spring-04: Factory Method utilizzando il container IoC di Spring

S

Introduzione

Dopo i primi tre articoli continuiamo il viaggio nelle funzionalità del framework Spring.
In questo appuntamento vedremo come Spring ci permette di istanziare bean nel container IoC secondo quelle che sono le caratteristiche del pattern Factory Method. Come già visto questo pattern ci permette di richiedere bean senza conoscere i dettagli implementativi riguardo la creazione degli stessi.

Negli esempi che seguiranno si farà riferimento alle classi definite nell’articolo precedente.

Static Factory Method

In alcuni casi potrebbe essere utile creare un bean invocando uno static factory method, il cui scopo è quello di incapsulare il processo di creazione di un oggetto in un metodo statico.
Il client che richiede un oggetto pu semplicemente chiamare il metodo senza conoscere i dettagli della creazione dello stesso ne specificare la classe.
Vediamo il dettaglio della classe che implementa il metodo statico di factory.

public class ShapeFactory {

  public static Shape createShape(String shapeType, String color){

    if(shapeType == null){
      throw new IllegalArgumentException("Unknown shape");
    }		

    if(shapeType.equalsIgnoreCase("RECTANGLE")){
      return new Rectangle(color);
    } else if(shapeType.equalsIgnoreCase("SQUARE")){
      return new Square(color);
    } else if(shapeType.equalsIgnoreCase("CIRCLE")){
      return new Circle(color);
    }

    throw new IllegalArgumentException("Unknown shape");
  }
}

La nostra classe Factory non fa altro che ritornare una nuova istanza di un oggetto decidendo la classe a runtime in base al tipo passato al metodo factory.

Vediamo di seguito la configurazione Spring:

<bean name="square" class="it.italiancoders.demospring3.types.ShapeFactory"
  factory-method="createShape">
  <constructor-arg value="Square" />
  <constructor-arg value="Red" />
</bean>

<bean name="rectangle" class="it.italiancoders.demospring3.types.ShapeFactory"
  factory-method="createShape">
  <constructor-arg value="Rectangle" />
  <constructor-arg value="Blue" />
</bean>

<bean name="circle" class="it.italiancoders.demospring3.types.ShapeFactory"
  factory-method="createShape">
  <constructor-arg value="Circle" />
  <constructor-arg value="Yellow" />
</bean>

<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService">
  <constructor-arg>
    <list>
      <ref bean="square" />
      <ref bean="rectangle" />
      <ref bean="circle" />
    </list>
  </constructor-arg>
</bean>

Dalla definizione vediamo come i nostri bean vengono istanziati utilizzando la classe ShapeFactory invocando il factory method createShape che istanzia bean di tipo Shape decidendo la classe in base al valore  shapeType  passato al costruttore.

Questo è l’output che otterremmo eseguendo il codice.

Drow a Square of color Red and border 1
Drow a Rectangle of color Blue and border 1
Drow a Circle of color Yellow and border 1

Instance Factory Method

A differenza del caso Static Factory Method potrebbe esserci la necessità di dover creare un bean invocando un factory method non statico ma questa volta di un istanza di un oggetto.
Anche in questo caso il client può richiedere un oggetto semplicemente invocando il metodo e senza conoscere i dettagli della creazione dello stesso ne specificare la classe.

public class ShapeFactory {

  private Map<String, Shape> shapes;

  public void setShapes(Map<String, Shape> shapes) {
    this.shapes = shapes;
  }

  public Shape createShape(String shapeType){

    Shape shape = shapes.get(shapeType);

    if(shape != null) {
      return shape;
    } else {
      throw new IllegalArgumentException("Unknown shape");
    }
  }
}

In questo caso la classe Factory contiene una Map di oggetti di tipo Shape. Il metodo factory si limita a ritornare il riferimento ad uno di essi in base al tipo passato come parametro.

Di seguito mostriamo la nuova configurazione:

<bean name="shapeFactory" class="it.italiancoders.demospring3.types.ShapeFactory">
  <property name="shapes">
    <map>
      <entry key="Square">
        <bean class="it.italiancoders.demospring3.types.Square">
          <property name="color" value="Red" />
          <property name="border" value="2" />
        </bean>
      </entry>
      <entry key="Rectangle">
        <bean class="it.italiancoders.demospring3.types.Rectangle">
          <property name="color" value="Blue" />
          <property name="border" value="3" />
        </bean>
      </entry>
      <entry key="Circle">
        <bean class="it.italiancoders.demospring3.types.Circle">
          <property name="color" value="Yellow" />
          <property name="border" value="1" />
        </bean>
      </entry>
    </map>
  </property>
</bean>

<bean name="square" factory-bean="shapeFactory" factory-method="createShape">
  <constructor-arg value="Square" />
</bean>

<bean name="rectangle" factory-bean="shapeFactory" factory-method="createShape">
  <constructor-arg value="Rectangle" />
</bean>

<bean name="circle" factory-bean="shapeFactory" factory-method="createShape">
  <constructor-arg value="Circle" />
</bean>

<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService">
  <constructor-arg>
    <list>
      <ref bean="square" />
      <ref bean="rectangle" />
      <ref bean="circle" />
    </list>
  </constructor-arg>
</bean>

Vediamo la definizione di un bean ShapeFactory a cui vengono passati tre bean di tipo Shape. Fatto questo, qualora ci sia necessità di definire altri bean di tipo Shape sarà sufficiente utilizzare il factory method createShape del factory bean shapeFactory, il quale ritornerà il riferimento ad uno dei bean precedentemente creati in base al tipo passato nel costruttore, che verrà a sua volta passato come parametro al metodo createShape e utilizzato per ricavare l’istanza da ritornare (se presente).

L’output risultate dall’esecuzione del nuovo codice sarà:

Drow a Square of color Red and border 2
Drow a Rectangle of color Blue and border 3
Drow a Circle of color Yellow and border 1

Factory Bean

Un factory bean è un bean che ha lo scopo di creare altri bean all’interno del container IoC.
Concettualmente un factory bean è molto simile ad un factory method, ma a differenza di quanto dei due esempi sopra è un bean Spring-specific bean che estende la classe astratta org.springframework.beans.factory.config.AbstractFactoryBean.

Vediamo l’implementazione:

public class ShapeFactoryBean extends AbstractFactoryBean<Shape> {
  
  private String shapeType;
  private String color;
  private int border;

  public void setShapeType(String shapeType) {
    this.shapeType = shapeType;
  }

  public void setColor(String color) {
    this.color = color;
  }

  public void setBorder(int border) {
    this.border = border;
  }

  @Override
  protected Shape createInstance() throws Exception {
    if(shapeType == null){
      throw new IllegalArgumentException("Unknown shape");
    }		
    Shape shape = null;
    if(shapeType.equalsIgnoreCase("RECTANGLE")){
      shape = new Rectangle(color);
    } else if(shapeType.equalsIgnoreCase("SQUARE")){
      shape = new Square(color);
    } else if(shapeType.equalsIgnoreCase("CIRCLE")){
      shape = new Circle(color);
    }
    
    shape.setBorder(border * 2);
    return shape;
  }

  @Override
  public Class<?> getObjectType() {
    return Shape.class;
  }
}

Estendere la classe astratta AbstractFactoryBean implica implementare i metodi createInstancegetObjectType, utilizzati rispettivamente per definire la logica con cui istanziare bean e il tipo degli stessi.

Vediamo ora la configurazione:

<bean name="square" class="it.italiancoders.demospring3.utils.ShapeFactoryBean">
  <property name="shapeType" value="Square" />
  <property name="color" value="Red" />
  <property name="border" value="2" />
</bean>

<bean name="rectangle" class="it.italiancoders.demospring3.utils.ShapeFactoryBean">
  <property name="shapeType" value="Rectangle" />
  <property name="color" value="Blue" />
  <property name="border" value="3" />
</bean>

<bean name="circle" class="it.italiancoders.demospring3.utils.ShapeFactoryBean">
  <property name="shapeType" value="Circle" />
  <property name="color" value="Yellow" />
  <property name="border" value="1" />
</bean>

<bean name="shapeService" class="it.italiancoders.demospring3.components.ShapeService">
  <constructor-arg>
    <list>
      <ref bean="square" />
      <ref bean="rectangle" />
      <ref bean="circle" />
    </list>
  </constructor-arg>
</bean>

Come possiamo vedere dalla definizione i nostri bean vengono istanziati utilizzando la classe ShapeFactoryBean. Internamente Spring fa un invocazione al metodo createInstance che istanzia bean di tipo Shape decidendo la classe in base al valore della property shapeType . Per simulare uno scopo aggiuntivo alla creazione del bean abbiamo deciso di far “customizzare” alla classe Factory l’oggetto modificando l’attributo border di un fattore 2. Questo è un esempio semplice, ma rappresenta una necessità aggiuntiva che si può aggiungere ad un factory method.

Mostriamo ancora una volta l’output che otterremmo eseguendo il codice.

Drow a Square of color Red and border 4
Drow a Rectangle of color Blue and border 6
Drow a Circle of color Yellow and border 2

Conclusione

Il pattern Factory Method è un pattern molto importate, potente e utile in tutti quei casi in cui abbiamo la necessità di accedere a delle istanze di bean nascondendo i dettagli relativi alla creazione. In alcuni dei casi sopra abbiamo anche visto come “customizzare” in una classe Factory istanza al momento della creazione.

Questo articolo mette ancora una volta in risalto le potenzialità e la flessibilità che Spring offre nel definire bean all’interno del suo container IoC sfruttando il pattern Factory Method.

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.

Gli articoli più letti

Articoli recenti

Commenti recenti