Refatoração de Código: Unificar Interfaces Com Adapter

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog. Você receberá um email de confirmação. Somente depois de confirma-lo é que poderei lhe enviar os conteúdos exclusivos.

Email inválido.
Blog /Android /Refatoração de Código: Unificar Interfaces Com Adapter

Refatoração de Código: Unificar Interfaces Com Adapter

Vinícius Thiengo
(1194)
Go-ahead
"Sempre procure algo que possa ser aprimorado. Nunca, jamais, conforme-se com o lugar onde está. A maneira como você atinge seus objetivos é sempre experimentar, até que consiga perceber se atingiu um ponto mais alto."
Jeff Sutherland
Kotlin Android
Capa do livro Desenvolvedor Kotlin Android - Bibliotecas para o dia a dia
TítuloDesenvolvedor Kotlin Android - Bibliotecas para o dia a dia
CategoriasAndroid, Kotlin
AutorVinícius Thiengo
Edição
Capítulos19
Páginas1035
Acessar Livro
Treinamento Oficial
Android: Prototipagem Profissional de Aplicativos
CursoAndroid: Prototipagem Profissional de Aplicativos
CategoriaAndroid
InstrutorVinícius Thiengo
NívelTodos os níveis
Vídeo aulas186
PlataformaUdemy
Acessar Curso
Receitas Android
Capa do livro Receitas Para Desenvolvedores Android
TítuloReceitas Para Desenvolvedores Android
CategoriaDesenvolvimento Android
AutorVinícius Thiengo
Edição
Ano2017
Capítulos20
Páginas936
Acessar Livro
Código Limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas599
Acessar Livro
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Conteúdo Exclusivo
Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Opa, blz?

Nesse artigo damos continuidade a série sobre Refatoração de Código para obter projetos de software mais eficientes. Dessa vez abordando o método de refatoração: Unificar Interfaces com Adapter.

Antes de prosseguir informo que os métodos de refatoração dessa série e também os padrões abordados, todos podem ser utilizados em qualquer projeto de software orientado a objetos, não somente os de Android.

Esse método de refatoração tem como pré-requisito o conhecimento do padrão de projeto Adapter, logo abaixo deixo o link do artigo aqui do Blog somente sobre esse padrão.

É necessário também o conhecimento do método de refatoração Extrair Classe, mas esse é bem simples, então explico abaixo.

Tópicos presentes no artigo:

Método de refatoração Extrair Classe

Esse método é muito simples e dispensa um artigo somente para ele. Em outros conteúdos dessa série já o expliquei, mas caso esteja tendo conhecimento dele somente agora, segue explanação.

Com esse método é possível criar uma nova classe partindo de outra classe que realiza mais de uma tarefa principal, com isso criando uma melhor divisão de código no projeto. Lembrando que um dos princípios de programação orientada a objetos é que cada classe deve ter somente uma única tarefa principal.

Esse método de refatoração é comumente aplicado em classes grandes, onde somente o tamanho delas já é um indício forte de que há mais de uma tarefa principal como responsabilidade dessas classes.

Motivação

Você tem um projeto de software que tem dentro dele algumas classes trabalhando com interfaces similares.

Enquanto essas classes clientes permanecem trabalhando cada uma com sua própria interface, a manutenção do código continua ineficiente, a evolução para um código sem repetição fica dificultada.

Vale relembrar

O termo “interface" utilizado aqui é, na verdade, de significado no contexto geral, ou seja, pode ser uma Interface estrutura de linguagem, classe concreta ou classe abstrata.

Logo, você escolhe entre as interfaces comuns a que melhor se aplica a seu projeto. Então poderá criar um código cliente que utilize as interfaces similares por meio de uma interface comum, descomplicando e colocando ele no caminho certo para outras otimizações para um código limpo.

Código de exemplo

Para nosso exemplo com o método proposto aqui, vamos utilizar um código de builders de tags.

Tags?

Isso mesmo. Tags XML ou HTML. Antes de prosseguirmos com os códigos de exemplo, vamos primeiro visualizar o diagrama desse projeto:

Nesse projeto XMLBuilder e DOMBuilder são classes clientes que utilizam interfaces similares. As interfaces similares são: a classe TagNode e a Interface Element.

Para podermos evoluir o código ainda mais, temos que primeiro deixar o código cliente (XMLBuilder e DOMBuilder) utilizando essas interfaces similares por meio de uma interface comum.

Com isso nosso objetivo será construir essa interface comum. Mas antes vamos aos trechos de código cliente que nos interessa em XMLBuilder e DOMBuilder. Com eles será possível ver as interfaces similares, TagNode e Element, trabalhando. Começando por XMLBuilder:

public class XMLBuilder extends AbstractBuilder {
private TagNode rootNode;
private TagNode currentNode;

public void addChild( String childTagName ){
addTo( currentNode, childTagName );
}

public void addSibling( String siblingTagName ){
addTo( currentNode.getParent(), siblingTagName );
}

private void addTo( TagNode parentNode, String tagName ){
currentNode = new TagNode( tagName );
parentNode.add( currentNode );
}

public void addAttribute( String name, String value ){
currentNode.addAttribute( name, value );
}

public void addValue( String value ){
currentNode.addValue( value );
}
...
}

 

Logo depois o código de DOMBuilder:

public class DOMBuilder extends AbstractBuilder {
private Document document;
private Element root;
private Element parent;
private Element current;

public void addAttribute( String name, String value ){
current.setAttribute( name, value );
}

public void addChild( String child ){
Element childNode = document.createElement( child );
current.appendChild( childNode );
parent = current;
current = childNode;
}

public void addSibling( String sibling ){
if( current == null ){
throw new RuntimeException();
}

Element siblingNode = document.createElement( sibling );
parent.appendChild( siblingNode );
current = siblingNode;
}

public void addValue( String value ){
current.appendChild( document.createTextNode( value ) );
}
...
}

 

Não custa lembrar que: XMLBuilder e DOMBuilder são as classes clientes que utilizam interfaces similares, TagNode e Element.

Mecânica

Nosso primeiro passo tem duas tarefas. A primeira é escolher a interface que será utilizada como interface comum. Em nosso caso: TagNode ou Element. Optei por TagNode, pois os métodos dela são simples, com poucas linhas de código, porém você poderia seguir com Element sem problemas caso prefira assim.

Definida a interface comum, a tarefa dois é criar nossa Interface que será realmente a interface comum. Dessa vez é uma Interface mesmo, estrutura de linguagem. Essa Interface deve ser extraída da classe TagNode, pois foi essa que optamos como interface preferida.

Segue código da nova Interface comum, XMLNode:

public interface XMLNode {
public void add( XMLNode childNode );
public void addAttribute( String attribute, String value );
public void addValue( String value );
}

 

Você provavelmente deve estar se perguntando: como saber que esses são os métodos a serem também parte da Interface comum?

Nessa parte é importante lembrar que no código de XMLBuilder, o builder que utiliza TagNode, somente três métodos de TagNode estavam sendo utilizados, os três que permitiam que XMLBuilder trabalhasse de forma muito similar a DOMBuilder com seu Element.

Então, no código de XMLBuilder apresentado, já tinhamos os métodos que deveriam ir para nossa interface comum (por isso somente os trechos necessários foram apresentados).

Caso tivéssemos escolhido Element, por ela ser uma Interface, já poderíamos trabalhar diretamente com ela sendo a Interface comum, não teria problemas.

Bom, explicado isso, ainda temos que, no passo um, atualizar o código da entidade de onde extraímos a interface comum, TagNode. A atualização é: implementar a Interface e alterar todos os métodos que aceitem um objeto do tipo TagNode como parâmetro, esses deverão passar a aceitar objetos do tipo da Interface criada ao invés de tipos TagNode diretamente.

Recapitulando o código cliente de TagNode, XMLBuilder:

public class XMLBuilder extends AbstractBuilder {
...

private void addTo( TagNode parentNode, String tagName ){
currentNode = new TagNode( tagName );
parentNode.add( currentNode );
}
...
}

 

Nossa atualização em TagNode:

public class TagNode implements XMLNode {
public void add( XMLNode childNode ){
...
}
}

 

Agora é que você deve estar completamente perdido, pois a interface comum extraída de TagNode tem somente alguns dos métodos dele, há ainda outros métodos públicos e privados.

O que está acontecendo?

Primeiro, o processo de extrair uma Interface de uma classe que já tínhamos escolhido como interface comum é completamente aceitável, pois mesmo que tenhamos escolhido TagNode, ela ainda tem métodos que não nos interessam na comunicação com o código cliente, digo, métodos que são chamados somente internamente, por exemplo.

Caso tivéssemos optado pela Interface Element como interface comum, como já dito anteriormente, poderíamos tratá-la diretamente como a verdadeira Interface comum, sem precisar extrair uma outra. Com isso pularíamos alguns pontos do passo um.

TagNode forneceu a interface para a criação da Interface XMLNode. Agora, lembrando do padrão Adapter, temos um adaptador e uma entidade adaptada. Como TagNode forneceu a interface, ela não precisa ser adaptada, porém o código dela que trabalha com referências a objetos do tipo próprio dela precisa sim ser alterado para XMLNode.

Pois dessa forma, TagNode poderá continuar sendo utilizada por XMLBuilder e DOMBuilder. Poderá também utilizar uma classe adaptadora (ainda vamos criá-la) que implementará XMLNode e que terá uma instância de Element como composição.

Com isso, ambos os códigos clientes vão terminar trabalhando com uma única interface,  a da Interface XMLNode.

Agora vamos ao passo dois. A partir daqui vamos trabalhar na classe adaptadora e na entidade adaptada. Na classe cliente, DOMBuilder, que tem a entidade que deve ser adaptada, Element, aplicamos um simples método de refatoração, Extrair Classe. Temos então, ElementAdapter:

public class ElementAdapter {
private Element element;

public ElementAdapter( Element element ){
this.element = element;
}

public Element getElement(){
return element;
}
}

 

Nesse início, definição de classe, precisamos ter somente uma referência a entidade que está sendo adaptada, Element, um método para obter essa entidade e uma maneira de mudar o valor da variável de instância, por meio de um método específico ou por meio do construtor. Aqui optamos pelo construtor.

Nosso passo três é atualizar o código cliente da classe que está sendo adaptada para começar a utilizar a classe adaptadora no lugar dela. Ou seja, onde tem Element, vamos colocar ElementAdapter. E onde tem element.metodo() vamos colocar elementAdapter.getElement().metodo(). Seguem trechos atualizados de DOMBuilder:

public class DOMBuilder extends AbstractBuilder {
...

public void addAttribute( String name, String value ){
current.getElement().setAttribute( name, value );
}

public void addChild( String child ){
ElementAdapter childNode = new ElementAdapter( document.createElement( child ) );
current.getElement().appendChild( childNode.getElement() );
parent = current;
current = childNode;
}

public void addSibling( String sibling ){
if( current == null ){
throw new RuntimeException();
}

ElementAdapter siblingNode = new ElementAdapter( document.createElement( sibling ) );
parent.getElement().appendChild( siblingNode.getElement() );
current = siblingNode;
}

public void addValue( String value ){
current.getElement().appendChild( document.createTextNode( value ) );
}
}

 

O quarto passo é simples. No código cliente da entidade adaptada, onde tivermos invocação do método da classe adaptadora que permite acesso a entidade adaptada, mais precisamente chamadas ao método getElement(). Nesses pontos vamos extrair métodos que permitam o encapsulamento dessas chamadas encadeadas.

Complicado, não? Não. Segue exemplo. O código abaixo ...:

...
ElementAdapter childNode = new ElementAdapter( ... );
parent.getElement().appendChild( childNode.getElement() );
...

 

... deverá se tornar isso:

...
ElementAdapter childNode = new ElementAdapter( ... );
appendChild( parent, childNode );
...

 

E então o novo método extraído, appendChild():

private void appendChild( ElementAdapter parent, ElementAdapter childNode ){
parent.getElement().appendChild( childNode.getElement() );
}

 

Note que é necessário que o objeto adaptador, que estava invocando o método de acesso a entidade adaptada, seja também parâmetro do novo método criado, independente se ele é ou não uma variável de instância. Se houver mais do que um objeto adaptador, passe todos como argumento do método. No exemplo acima tínhamos os objetos parent e childNode.

Com isso podemos prosseguir com a atualização do código cliente, DOMBuilder:

public class DOMBuilder extends AbstractBuilder {
...

public void addAttribute( String name, String value ){
addAttribute( current, name, value ); /* NOVA CHAMADA */
}

public void addChild( String child ){
ElementAdapter childNode = new ElementAdapter( document.createElement( child ) );
add( current, childNode ); /* NOVA CHAMADA */
parent = current;
current = childNode;
}

public void addSibling( String sibling ){
if( current == null ){
throw new RuntimeException();
}

ElementAdapter siblingNode = new ElementAdapter( document.createElement( sibling ) );
add(parent, siblingNode); /* NOVA CHAMADA */
current = siblingNode;
}

public void addValue( String value ){
addValue( current, value ); /* NOVA CHAMADA */
}

/* NOVOS MÉTODOS */

private void addAttribute( ElementAdapter current, String name, String value ){
current.getElement().setAttribute( name, value );
}

private void add( ElementAdapter current, ElementAdapter childNode ){
current.getElement().appendChild( childNode.getElement() );
}

private void addValue( ElementAdapter current, String value ){
current.getElement().appendChild( document.createTextNode( value ) );
}
}

 

Notou algo?

Isso mesmo, os métodos criados não têm um padrão no nome, na verdade esses métodos são criados com esses nomes para já adiantarem o processo de implementação da interface comum, a Interface XMLNode, na classe adaptadora, ElementAdapter.

Você poderia ter colocado outros nomes neles, mas o passo de mudança de nome devido a implementação da Interface comum seria inevitável.

Nosso quinto passo é mover esses novos métodos extraídos para a classe adaptadora, ElementAdapter. Note que devemos manter os métodos iguais aos correspondentes na interface comum, XMLNode. Segue atualização em ElementAdapter:

public class ElementAdapter {
private Element element;

public ElementAdapter( Element element ){
this.element = element;
}

public Element getElement(){
return element;
}

/* MÉTODOS ADICIONADOS */

private void addAttribute( String name, String value ){
getElement().setAttribute( name, value );
}

private void add( ElementAdapter childNode ){
getElement().appendChild( childNode.getElement() );
}
}

 

Fizemos pequenas modificações nos métodos, até porque não era mais necessária a utilização do parâmetro do tipo ElementAdapter que invocava getElement(), ele agora é o objeto em si.

E o addValue()?

Bem lembrado. Esse, devido a utilização de uma entidade exclusiva de DOMBuilder, o document, deve provocar uma atualização a mais em ElementAdapter, pois queremos as assinaturas dos métodos adicionados iguais aos dos métodos da interface comum.

Para passarmos um objeto Document para ElementAdapter podemos utilizar o construtor ou um método somente para essa atribuição em ElementAdapter. Vamos optar pelo construtor. Segue código atualizado:

public class ElementAdapter {
private Element element;
private Document document;

public ElementAdapter( Element element, Document document ){
this.element = element;
this.document = document;
}

...

private void addValue( String value ){
getElement().appendChild( document.createTextNode( value ) );
}
}

 

E agora o DOMBuilder atualizado, construtor e algumas chamadas de métodos:

public class DOMBuilder extends AbstractBuilder {
...

public void addAttribute( String name, String value ){
current.addAttribute( name, value ); /* ATUALIZADO */
}

public void addChild( String child ){
ElementAdapter childNode = new ElementAdapter( document.createElement( child ), document );
current.add( childNode ); /* ATUALIZADO */
parent = current;
current = childNode;
}

public void addSibling( String sibling ){
if( current == null ){
throw new RuntimeException();
}

ElementAdapter siblingNode = new ElementAdapter( document.createElement( sibling ), document );
parent.add( siblingNode ); /* ATUALIZADO */
current = siblingNode;
}

public void addValue( String value ){
current.addValue( value ); /* ATUALIZADO */
}
}

 

Prosseguindo, no passo seis apenas precisamos fazer com que nossa classe adaptadora implemente a interface comum, XMLNode. Algumas pequenas modificações serão necessárias:

public class ElementAdapter implements XMLNode {
...

private void add( XMLNode childNode ){
ElementAdapter childElement = (ElementAdapter) childNode;
getElement().appendChild( childElement.getElement() );
}

...
}

 

Além de implementar XMLNode foi necessário atualizar add() para trabalhar com XMLNode, passo similar ao passo um, com a classe TagNode. Com isso temos nossa interface comum sendo aplicada as duas interfaces similares, TagNode e Element, por meio do adapter ElementAdapter.

Ainda temos um último passo, o sétimo. Devemos agora atualizar DOMBuilder onde todos os atributos, parâmetros e variáveis locais que são do tipo ElementAdapter passem a ser do tipo XMLNode, segue:

public class DOMBuilder extends AbstractBuilder {
private Document document;
private XMLNode root;
private XMLNode parent;
private XMLNode current;

public void addAttribute( String name, String value ){
current.addAttribute( name, value );
}

public void addChild( String child ){
XMLNode childNode = new ElementAdapter( document.createElement( child ), document );
current.add( childNode );
parent = current;
current = childNode;
}

public void addSibling( String sibling ){
if( current == null ){
throw new RuntimeException();
}

XMLNode siblingNode = new ElementAdapter( document.createElement( sibling ), document );
parent.add( siblingNode );
current = siblingNode;
}

public void addValue( String value ){
current.addValue( value );
}
}

 

Com isso terminamos nossa refatoração. Agora ambas as classes clientes, XMLBuilder e DOMBuilder, utilizam exatamente a mesma interface comum para trabalho com TagNode e Element.

Conclusão

O que temos no final é um código repetido que pode facilmente ser atualizado para versões mais eficientes, sem repetição de código. Ou seja, o que fizemos aplicando essa refatoração para o padrão Adapter foi "abrir o caminho" para a aplicação de outras refatorações.

As duas refatorações mais evidentes, a princípio, são: Formar Template Method e Introduzir Criação Polimórfica com Factory Method.

Mesmo com essa ideia de “abrir o caminho” o código ficou melhor, principalmente devido a utilizarão de uma interface única no código cliente, permitindo a fácil adição ou remoção de novas entidades que implementem a interface comum.

Outros artigos da série

Abaixo, a lista dos artigos já liberados, dos métodos de refatoração, dessa série sobre refatoração de código:

Internalizar Singleton

Mover Embelezamento Para Decorator

Substituir Condicionais que Alteram Estado por State

Introduzir Objeto Nulo

Extrair Adapter

Mover Conhecimento de Criação Para Factory

Substituir Notificações Hard-Coded Por Observer

Substituir Código de Tipo Por Classe

Extrair Parâmetro

Unificar Interfaces

Limitar Instanciação Com Singleton

Mover Acumulação Para Parâmetro Coletor

Compor Method

Formar Template Method

Substituir Lógica Condicional Por Strategy

Introduzir Criação Polimórfica com Factory Method

Encapsular Classes Com Factory

Encadear Construtores

Substituir Construtores Por Métodos de Criação

Fontes

Refatoração Para Padrões

Vlw.

Receba em primeira mão, e com prioridade, os conteúdos Android exclusivos do Blog.
Email inválido

Relacionado

Padrão de Projeto: Simple FactoryPadrão de Projeto: Simple FactoryAndroid
Padrão de Projeto: Factory MethodPadrão de Projeto: Factory MethodAndroid
Padrão de Projeto: Abstract FactoryPadrão de Projeto: Abstract FactoryAndroid
Padrão de Projeto: AdapterPadrão de Projeto: AdapterAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...