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.


