Mão na massa: Chain of Responsibility

Problema:

Uma aplicação de e-commerce precisa se comunicar com vários bancos diferentes para prover aos seus usuários mais possibilidades de pagamentos, atingindo assim um número maior de usuários e facilitando suas vidas.

Ao modelar uma forma de execução do pagamento, dado que precisamos selecionar um entre vários tipos de bancos, a primeira ideia que surge é utilizar uma estrutura de decisão para verificar, dado um parâmetro, qual o banco correto deve ser utilizado.

já discutimos no post sobre o padrão Strategy que utilizar uma estrutura de decisão pode ser muito complexo, e vimos uma boa solução para este problema. Também vimos exemplos de utilização de estruturas de decisão nos padrõs Abstract Factory e Factory Method.

Poderíamos utilizar os métodos fábricas para gerar o objeto correto para ser utilizado na nossa aplicação. Poderíamos criar estratégias diferentes para cada banco e escolher em tempo de execução.

Em todas estas soluções, nós utilizamos uma forma de “esconder” a estrutura de decisão por trás de uma interface, ou algo similar, para que as alterações fossem menos dolorosas. No entanto, continuamos utilizando as estruturas de decisão.

Vamos analisar então o padrão Chain of Responsibility, que promete acabar com estas estruturas.

Chain of Responsibility

O padrão Chain of Responsibility possui a seguinte intenção:

“Evitar o acoplamento do remetente de uma solicitação ao seu receptor, ao dar a mais de um objeto a oportunidade de tratar a solicitação. Encadear os objetos receptores, passando a solicitação ao longo da cadeia até que um objeto a trate.” [1]

Pela intenção percebemos como o Chain of Responsibility acaba com as estruturas de decisão, ele cria uma cadeia de objetos e vai passando a responsabilidade entre eles até que alguém possa responder pela chamada.

Vamos então iniciar construindo uma pequena enumeração para identificar os bancos utilizados no nosso sistema:

public enum IDBancos {
	bancoA, bancoB, bancoC, bancoD 
}

Agora vamos construir a classe que vai implementar a cadeia de responsabilidades. Vamos exibir partes do código para facilitar o entendimento.

public abstract class BancoChain {

	protected BancoChain next;
	protected IDBancos identificadorDoBanco;

	public BancoChain(IDBancos id) {
		next = null;
		identificadorDoBanco = id;
	}

	public void setNext(BancoChain forma) {
		if (next == null) {
			next = forma;
		} else {
			next.setNext(forma);
		}
	}
}

A nossa classe possui apenas dois atributos, o identificador do banco e uma referência para o próximo objeto da corrente. No construtor inicializamos estes atributos. O método setNext recebe uma nova instância da classe e faz o seguinte:

Se o próximo for nulo, então o próximo na corrente será o parâmetro. Caso contrário, repassa esta responsabilidade para o próximo elemento. Assim, a instância que deve ser adicionada na corrente irá percorrer os elementos até chegar no último elemento.

O próximo passo será criar o método para efetuar o pagamento.

	public void efetuarPagamento(IDBancos id) throws Exception {
		if (podeEfetuarPagamento(id)) {
			efetuaPagamento();
		} else {
			if (next == null) {
				throw new Exception("banco não cadastrado");
			}
			next.efetuarPagamento(id);
		}
	}

	private boolean podeEfetuarPagamento(IDBancos id) {
		if (identificadorDoBanco == id) {
			return true;
		}
		return false;
	}

	protected abstract void efetuaPagamento();

A primeira parte do algoritmo de pagamento é verificar se o banco atual pode fazer o pagamento. Para isto é utilizado o identificador do banco, que é comparado com o identificador passado por parâmetro. Se o elemento atual puder responder a requisição é chamado o método que vai efetuar o pagamento de fato. Este método é abstrato, e as subclasses devem implementá-lo, com seu próprio mecanismo.

Se o elemento atual não puder responder, ele repassa a chamado ao próximo elemento da lista. Antes disto é feita uma verificação, por questões de segurança, se este próximo elemento realmente existe. Caso nenhum elemento possa responder, é disparada uma exceção.

Agora que definimos a estrutura da cadeia de responsabilidades, vamos implementar um banco concreto, que responde a uma chamada.

public class BancoA extends BancoChain {

	public BancoA() {
		super(IDBancos.bancoA);
	}

	@Override
	protected void efetuaPagamento() {
		System.out.println("Pagamento efetuado no banco A");
	}
}

O Banco A inicializa seu ID e, no método de efetuar o pagamento, exibe no terminal que o pagamento foi efetuado. A implementação dos outros bancos segue este exemplo. O ID é iniciado e o método de efetuar o pagamento exibe a saída no terminal.

O cliente deste código seria algo do tipo:

public static void main(String[] args) {
	BancoChain bancos = new BancoA();
	bancos.setNext(new BancoB());
	bancos.setNext(new BancoC());
	bancos.setNext(new BancoD());
	
	try {
		bancos.efetuarPagamento(IDBancos.bancoC);
		bancos.efetuarPagamento(IDBancos.bancoD);
		bancos.efetuarPagamento(IDBancos.bancoA);
		bancos.efetuarPagamento(IDBancos.bancoB);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

O diagrama UML deste exemplo seria:

Um pouco de teoria

Vimos pelo exemplo que o padrão Chain of Responsibility fornece uma maneira de tomar decisões com um fraco acoplamento. Perceba que a estrutura de cadeia não possui qualquer informação sobre as classes que compõem a cadeia, da mesma forma, uma classe da cadeia não tem nenhuma noção sobre o formato da estrutura ou sobre elementos nela inseridos.

Assim, é possível variar praticamente todos os componentes sem grandes danos ao projeto. Cada elemento implementa sua própria maneira de responder a requisição, e estas podem ser alteradas facilmente.

O problema é que é preciso tomar cuidado para garantir que as chamadas sejam realmente respondidas. No exemplo foi feita uma verificação para saber se o próximo elemento é nulo, para evitar uma acesso ilegal. Mas esta é uma solução para este problema específico. Cada problema exige o seu próprio cuidado.

Código fonte completo

O código completo pode ser baixado no seguinte repositório Git: https://github.com/MarcosX/Padr-es-de-Projeto.

Os arquivos estão como um projeto do eclipse, então basta colocar no seu Workspace e fazer o import.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Encontrou algum erro no código? Comente também. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.