Factory com CDI

Neste artigo, a partir de um exemplo simples de fábrica (Factory) na linguagem Java, vou  mostrar como implementar esta mesma fábrica, porém para criar objetos gerenciados pelo container, usando CDI.

Como exemplo vou usar uma fábrica de formas que, dado um tipo de forma, retorna uma instância deste tipo.

Para este exemplo teremos duas formas, o retângulo e o círculo, listadas na enumeração ShapeType.

public enum ShapeType {
   RECTANGLE, CIRCLE
}

A classe abstrata Shape representa uma forma genérica. Shape possui uma dependência do tipo ShapeDependency, que deve ser passada como parâmetro para o seu construtor.

public abstract class Shape {

   private ShapeDependency dependency;

   public Shape(ShapeDependency dependency) {
      this.dependency = dependency;
   }

   public ShapeDependency getDependency() {
      return dependency;
   }
}

A classe ShapeDependency por sua vez, possui a propriedade name, a qual deve ser  passada como parâmetro para o seu construtor.

public class ShapeDependency {

   private String name;

   public ShapeDependency(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }
}

As classes Rectangle e Circle são formas específicas, subclasses de Shape. Elas instanciam ShapeDependency nos seus construtores e passam esta instância para o construtor da superclasse Shape, já recebendo o name correto.

public class Rectangle extends Shape {

   public Rectangle() {
      super(new ShapeDependency("Rectangle dependency"));
   }
}

public class Circle extends Shape {

   public Circle() {
      super(new ShapeDependency("Circle dependency"));
   }
}

Por fim, a fábrica ShapeFactory possui o método create que retorna uma instância da classe Shape, de acordo com o ShapeType recebido por parâmetro.

public class ShapeFactory {

   public Shape create(ShapeType type) {
      if(RECTANGLE.equals(type)){
         return new Rectangle();
      }
      if(CIRCLE.equals(type)){
         return new Circle();
      }
      return null;
   }
}

Até aqui tudo tranquilo, nada diferente de uma fábrica comum. Mas e se a instância retornada pela fábrica possui uma dependência gerenciada pelo container?

Para ilustrar este caso, Shape terá a sua dependência injetada por CDI.

public abstract class Shape {

   @Inject
   private ShapeDependency dependency;

   public ShapeDependency getDependency() {
      return dependency;
   }
}

Rectangle e Circle teriam de configurar a dependência num método anotado com @PostConstruct, pois este será executado pelo container após a injeção da dependência, ao contrário do construtor, que é executado antes, o que ocasionaria NullPointerException.

public class Rectangle extends Shape {

   @PostConstruct
   public void setUp() {
      dependency.setName("Rectangle dependency");
   }
}

public class Circle extends Shape {

   @PostConstruct
   public void setUp() {
      dependency.setName("Circle dependency");
   }
}

ShapeDependency teria o valor da propriedade name configurado num setter, pois o container usa apenas o construtor vazio (sem parâmetros) para instanciar as classes que ele gerencia.

public class ShapeDependency {

   private String name;

   public void setName(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }
}

Neste novo contexto, o problema é que o método create de ShapeFactory não pode mais criar a instância de Shape com um comando new, pois esta nova instância não terá sua dependência injetada, já que com este comando é você que está gerenciando o ciclo de vida da instância, não o container.

Você também não pode simplesmente injetar Shape na ShapeFactory, pois Shape é uma classe abstrata e o container não saberia qual subclasse injetar, Rectangle ou Circle.

E mesmo que o container injetasse uma delas arbitrariamente, ele o faria ao instanciar ShapeFactory, e nós só sabemos de qual subclasse de Shape queremos uma instância durante a execução do método create, a partir do parâmetro ShapeType recebido.

A solução é usar um recurso que o CDI nos proporciona, que permite avisar ao container que ele não deve injetar a dependência (Shape) ao instanciar ShapeFactory, que é a interface Instance<T>. Esta interface ainda nos permite, no momento apropriado, selecionar a subclasse desejada através do método select, e obter a instância desta classe através do método get.

public class ShapeFactory {

   @Any
   @Inject
   private Instance<Shape> shape;

   public Shape create(ShapeType type) {
      if(RECTANGLE.equals(type)){
         return shape.select(Rectangle.class).get();
      }
      if(CIRCLE.equals(type)){
         return shape.select(Circle.class).get();
      }
      return null;
   }
}

Desta forma temos uma fábrica com o mesmo comportamento da primeira, mas que é capaz de entregar instâncias gerenciadas pelo container.

PS: Note que para funcionar a própria fábrica deve ser gerenciada pelo container.