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"); } }
E 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; } }
PS: Note que para funcionar a própria fábrica deve ser gerenciada pelo container.