Aplicando Abstract Factory
- #Java
- #Design Patterns
Abstract Factory é um padrão criacional na qual se define uma interface responsável pela criação de familias de objetos relacionados ou dependentes sem a necessidade de especificar suas classes concretas.
Muitos projetos começam com o Padrão Factory Method(acesse aqui para saber mais) e evoluem para o Abstract Factory.
Esse Padrão resolve o problema de dependencia de familas de obejetos relacionados, dando a flexibilidade para que seja desenvolvida a regra de negócio sem especificar de antemão as classes concretas, oferecendo a possibilidade para que se escale novas implementações no futuro.
Abaixo um diagrama do padrão:
+--------------------------------------+ +------------+
| <<interface>> AbstractFactory |<------------| Client |__
|--------------------------------------| |------------| |
| + createProductA(): AbstractProductA | +------------+ |
| + createProductB()| AbstractProductB | | |
+--------------------------------------+ | |
^ ^ | |
/ \ | |
/ \ | |
+--------------------+ +--------------------+ +--------------------+ |
| ConcreteFactory1 | | ConcreteFactory2 | | <<interface>> | |
|--------------------| |--------------------| | AbstractProductA | |
| + createProductA() | | + createProductA() | |--------------------| |
| + createProductB() | | + createProductB() | +--------------------+ |
+--------------------+ +--------------------+ ^ ^ |
| | | | / \ |
| | | | / \ |
| | | | +-----------+ +-----------+ |
| | | ---->| ProductA2 | | ProductA1 |<--- |
| | | |-----------| |-----------| | |
| | | +-----------+ +-----------+ | |
| | | | |
| | | +--------------------+ | |
| | | | <<interface>> | | |
| | | | AbstractProductB |<------|----
| | | |--------------------| |
| | | +--------------------+ |
| | | ^ ^ |
| | | / \ |
| | | / \ |
| | | +-----------+ +-----------+ |
| | ----------->| ProductB2 | | ProductB1 | |
| | |-----------| |-----------| |
| | +-----------+ +-----------+ |
| | ^ |
| | | |
| ------------------------------------------------------------- |
| |
---------------------------------------------------------------------------
Como exemplo de aplicação, imaginemos que temos uma operação de pagamento, em que para efetuar uma transação com sucesso é necessário uma adquirente e uma gestora de risco, ou seja, uma familia de 2 objetos.
Com essa ideia em mente, podemos assimilar que teriamos um classe Pagamento(Client) com um método autorizar, uma interface de Adquirente(AbstractProductA) com os métodos capturar e confirmar e também uma interface de GestoraRisco(AbstractProductB) com o método avaliar.
Com as abstrações de nossas dependencias(abstract product) em mãos, podemos já pensar em nossas fábricas de objetos, então daremos o nome em uma interface de ModuloPagamentoFactory(AbstractFactory) na qual teremos os métodos de criação criarAdquirente e criarGestoraRisco.
Pronto, agora nossa classe Pagamento já tem a possíbilidade de implementar uma transação através do método autorizar com os produtos abstratos que a fábrica entregará. Assim temos as seguintes interfaces de nossas abstrações:
// Produtos Abstratos
public interface GestoraRisco {
void avaliar(Cartao cartao, BigDecimal valor) throws AlertaDeRiscoException;
}
public interface Adquirente {
void capturar(Cartao cartao, BigDecimal valor) throws CapturaNaoAutorizadaException;
UUID confirmar();
}
// Fábrica Abstrata
public interface ModuloPagamentoFactory {
Adquirente criarAdquirente();
GestoraRisco criarGestoraDeRisco();
}
// Cliente
public class Pagamento {
private final Adquirente adquirente;
private final GestoraRisco gestoraDeRisco;
// Injeção das dependencias com a fábrica
public Pagamento(ModuloPagamentoFactory moduloPagamentoFactory) {
this.adquirente = moduloPagamentoFactory.criarAdquirente();
this.gestoraDeRisco = moduloPagamentoFactory.criarGestoraDeRisco();
}
public UUID autorizar(Cartao cartao, BigDecimal valor) throws CapturaNaoAutorizadaException, AlertaDeRiscoException{
this.adquirente.capturar(cartao, valor);
this.gestoraDeRisco.avaliar(cartao, valor);
return this.adquirente.confirmar();
}
}
Daí em diante, só necessitamos implementar as fábricas(ConcreteFactory1, ConcreteFactory2...) juntamente com seus produtos(ProductA1, ProductB1...). Nossas classes ficariam da seguinte maneira:
// Produtos Concretos
public class Cielo implements Adquirente {
private UUID codigoConfirmacao = null;
@Override
public void capturar(Cartao cartao, BigDecimal valor) throws CapturaNaoAutorizadaException {
// Lógica para capturar um cartão
}
@Override
public UUID confirmar() {
// Lógica para confirmar a transação retornando um código UUID
return this.codigoConfirmacao;
}
}
public class ClearSale implements GestoraRisco {
@Override
public void avaliar(Cartao cartao, BigDecimal valor) throws AlertaDeRiscoException {
// Lógica para avaliar risco
}
}
// Fábrica Concreta
public class FintechModuloPagamentoFactory implements ModuloPagamentoFactory {
@Override
public Adquirente criarAdquirente() {
return new Cielo();
}
@Override
public GestoraRisco criarGestoraDeRisco() {
return new CrearSale();
}
}
Com essa abordagem, poderiamos futuramente escalar mais implementações de pagamentos através da implementação de fábricas de objetos com seus determinados produtos implementados.
Para finalizar, é importante notar que é no momento da instância de Pagamento(Client) que é definido a fábrica com seus respecticos objetos relacionados:
class PagamentoViaFintechTest {
private Pagamento pagamento;
@BeforeEach
void setUp() {
ModuloPagamentoFactory moduloPagamentoFactory = new FintechModuloPagamentoFactory();
pagamento = new Pagamento(moduloPagamentoFactory);
}
@Test
void deveAutorizarVenda() throws AlertaDeRiscoException, CapturaNaoAutorizadaException {
UUID codigoAutorizacao = pagamento.autorizar(new Cartao("1234"), new BigDecimal("200"));
assertNotNull(codigoAutorizacao);
}
}


