Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação. Você receberá um email de confirmação. Somente depois de confirmar é que poderei lhe enviar o conteúdo exclusivo por email.

Email inválido.
Blog /Android /Refatoração de Código: Mover Conhecimento de Criação Para Factory

Refatoração de Código: Mover Conhecimento de Criação Para Factory

Vinícius Thiengo16/06/2016, Quinta-feira, às 02h
(373) (1) (7) (9)
Go-ahead
"Construa uma voz e uma opinião em primeiro lugar e, em seguida, se essas ressoam com o público, então você vai ter uma audiência."
Adam Carolla
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áginas598
Comprar Livro
Conteúdo Exclusivo
Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Opa, blz?

Nesse artigo damos continuidade a série sobre Refatoração de Código para uma melhor performance como developer. Dessa vez abordando o método de refatoração: Mover Conhecimento de Criação para Factory.

Para prosseguir é importante saber que tem algo imprescindível que você precisa conhecer para continuar a estudar o método proposto aqui.

O que?

O padrão Factory, mais precisamente as versões do padrão Factory e o método de refatoração Substituir Construtores Por Métodos de Criação.

Abaixo os links dos artigos do blog sobre cada uma das variações do Factory e do método de refatoração. Caso ainda não conheça esses conteúdos, estude-os na sequência apresentada, de cima para baixo. Os artigos são pequenos e simples. Segue:

O método de refatoração proposto aqui permite que você utilize qualquer uma das variações de Factory, por isso a importância de conhecê-los.

Antes de prosseguir é importante informar que todos os métodos de refatoração nessa série são úteis para qualquer linguagem de programação que permita a utilização do paradigma orientado a objetos.

Com isso podemos prosseguir com o método de refatoração Mover Conhecimento de Criação para Factory.

Tópicos presentes no artigo:

Motivação

Você tem um código onde as classes de negócio (ou classes do domínio do problema) estão utilizando frequentemente, e muitas vezes para classes idênticas, o operador de instanciação de classe, new.

Pode parecer algo inofensivo, mas na verdade há um mar de problemas na instanciação de uma classe concreta dentro de outra. Isso fica ainda mais crítico quando acontece  inúmeras vezes.

No cenário descrito anteriormente é criado um forte acoplamento entre as classes, algo que é prejudicial, principalmente, a evolução do software.

Qualquer modificação em uma classe fornecedora pode “quebrar” o código de classes clientes, além de muitas instanciações diretas não deixarem o código nada intuitivo.

Logo, o que nos resta é focar em desenhar uma solução que permita remover essa dependência de criação de objetos dentro das classes de negócio do sistema. Pois como já citado em outros artigos do blog: do tempo e dinheiro investidos na evolução de softwares, cerca de 90% é utilizado somente para leitura e entendimento de código mal escrito.

Quanto mais aplicarmos melhorias, melhor.

Código de exemplo

O código a seguir será o exemplo de implementação do método de refatoração proposto. Esse código é de um antigo projeto Java chamado HTMLParser.

O trecho de código que vamos trabalhar é um que já tem parte do script encapsulado, porém ainda é possível melhorá-lo colocando todo o algoritmo envolvido na criação de objetos dentro de uma classe feita somente para isso.

Segue código de StringNode, a classe que já tem parte do código encapsulado:

public class StringNode {
...

public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

if( shouldDecode ){
return new DecodingStringNode( textBuffer, textBegin, textEnd );
}
return StringNode( textBuffer, textBegin, textEnd );
}
...
}

 

Não se preocupe com a não apresentação da classe DecodingStringNode aqui, pois ela é apenas uma variação dentro do método de criação em StringNode. O que nos interessa realmente é saber desse método de criação e da variação de instâncias dentro dele.

Segue outra classe de negócio de HTMLParser, StringParser:

public class StringParser {
...

public Node find(...){
...

Node node = StringNode.createStringNode(
textBuffer,
textBegin,
textEnd,
parser.shouldDecodeNodes()
);

return node;
}
...
}

 

E então a classe Parser que é utilizada por StringParser:

public class Parser {
private boolean shouldDecodeNodes = false;

public void setNodeDecoding( boolean shouldDecodeNodes ){
this.shouldDecodeNodes = shouldDecodeNodes;
}

public boolean shouldDecodeNodes(){
return shouldDecodeNodes;
}
...
}

 

Note que somente as partes importantes para o entendimento do método de refatoração é que foram colocadas nas classes.

Mecânica

Nosso primeiro passo é fazer com que nosso código cliente (StringParser nesse exemplo) somente crie instâncias de StringNode por meio de método de criação.

Como já apresentado na seção Código de exemplo, StringNode já implementa esse método de criação, ele faz com que StringNode seja já uma implementação de Factory, mais precisamente do Simple Factory.

Se em seu caso não houver esse método de criação já presente na classe fornecedora, crie um utilizando o método de refatoração Substituir Construtores por Métodos de Criação.

Por que não apenas continuar com a StringNode trabalhando também como uma Factory? Já está implementado.

Até seria possível, porém além de estarmos colocando mais de um objetivo para a classe StringNode, pois ela já tem suas tarefas como "nodo do tipo String", algo que é ruim quando seguindo as boas práticas de orientação a objetos. Além do já informado, o método de criação tem referência a uma instância de uma subclasse de StringNode, deixando um forte acoplamento entre essas. Nesse caso, uma classe somente para criação responderia melhor ao objetivo de limpar o código.

Com isso podemos prosseguir para o passo dois. Devemos criar uma nova classe (ou Interface, dependendo de seu caso) que será a fábrica de tipos StringNode. Segue código de NodeFactory, nossa nova classe Factory:

public class NodeFactory {
/* TODO */
}

 

O nome é similar ao nome das entidades que serão criadas a partir dessa Factory, isso melhora a leitura do código.

Espere um pouco, o que seria: "…fábrica de tipos StringNode"?

No caso é uma entidade Factory que criará somente instancias de StringNode ou suas subclasses, que também são StringNode.

No terceiro passo devemos mover o método de criação de instância para a nova classe Factory. Em nosso caso o método de criação está em StringNode. Segue código atualizado de NodeFactory:

public class NodeFactory {

public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd,
boolean shouldDecode ){

if( shouldDecode ){
return new DecodingStringNode( textBuffer, textBegin, textEnd );
}
return StringNode( textBuffer, textBegin, textEnd );
}
}

 

E então, ainda no passo três, podemos seguramente remover o método de criação que ainda está em StringNode.

No passo quatro devemos corrigir o código cliente para trabalhar com criação de instâncias por meio da nova entidade Factory. Em nosso caso a classe cliente é StringParser. Segue código atualizado dessa classe:

public class StringParser {
...

public Node find(...){
...

Node node = NodeFactory.createStringNode(
textBuffer,
textBegin,
textEnd,
parser.shouldDecodeNodes()
);

return node;
}
...
}

 

O quinto e último passo é o mais complicado, veja o objetivo dele:

Todos os métodos que ainda são utilizados para a criação de instâncias por meio da nova Factory, todos eles devem ser movidos para essa Factory, pois ela deve ter todo o trabalho de criação possível realizado dentro dela.

Ok, mas não lembro de ter notado algum outro método envolvido na criação de instâncias de StringNode. Existe algum?

Na verdade tem um outro. O shouldDecodeNodes() da variável parser que é do tipo Parser. Vamos recapitular essa classe:

public class Parser {
private boolean shouldDecodeNodes = false;

public void setNodeDecoding( boolean shouldDecodeNodes ){
this.shouldDecodeNodes = shouldDecodeNodes;
}

public boolean shouldDecodeNodes(){
return shouldDecodeNodes;
}
...
}

 

Os métodos de escrita e obtenção de shouldDecodeNodes em Parser, além do próprio shouldDecodeNodes, são as entidades que ainda devemos ter em NodeFactory.

Porém não é possível simplesmente remover o código de Parser e colocá-lo em NodeFactory, pois Parser tem os próprios códigos clientes dele, além do mais esses códigos clientes podem nem mesmo saber da existência de NodeFactory.

A solução nesse caso é fazer com que os códigos clientes de Parser sejam também códigos clientes de NodeFactory, como StringParser. Logo, dentro do passo cinco, teremos mais passos.

Vamos começar com o passo A. Criar uma nova classe com as mesmas entidades que precisamos de Parser. Optei pelo nome StringNodeParsingOption:

public class StringNodeParsingOption {
private boolean decodeStringNodes = false;

public void setDecodeStringNodes( boolean decodeStringNodes ){
this.decodeStringNodes = decodeStringNodes;
}

public boolean shouldDecodeStringNodes(){
return decodeStringNodes;
}
}

 

Ainda no passo A, vamos alterar a classe Parser, mais especificamente vamos remover a variável shouldDecodeNodes e seus métodos de leitura e escrita para então trabalhar com um atributo do tipo StringNodeParsingOption.

Teremos de criar os métodos de leitura e escrita desse novo atributo em Parser. Segue código atualizado dessa classe:

public class Parser {
private StringNodeParsingOption stringNodeParsingOption = new StringNodeParsingOption();

public void setStringNodeParsingOption( StringNodeParsingOption option ){
stringNodeParsingOption = option;
}

public StringNodeParsingOption getStringNodeParsingOption(){
return stringNodeParsingOption;
}
...
}

 

Os códigos clientes de Parser devem ser atualizados, pois a decodificação em StringNode é agora ativada por uma instância de StringNodeParsingOption. Segue um exemplo de código cliente atualizado:

public class DecodingNodeTest {
...

public void testDecodeAmpersand(){
...
StringNodeParsingOption decodeNodes = new StringNodeParsingOption();
decodeNodes.setDecodeStringNodes( true );
parser.setStringNodeParsingOption( decodeNodes );
}
...
}

 

Agora atualizando a classe StringParser temos:

public class StringParser {
...

public Node find(...){
...

Node node = NodeFactory.createStringNode(
textBuffer,
textBegin,
textEnd,
parser.getStringNodeParsingOption().shouldDecodeStringNodes()
);

return node;
}
...
}

 

Nosso passo B é mesclar as classe StringNodeParsingOption com NodeFactory. A princípio vamos optar por colocar os métodos e atributos de NodeFactory em StringNodeParsingOption, pois a quantidade de código movido será menor, em nosso caso somente temos uma entidade em NodeFactory, o método de criação que tinhamos movido para essa classe. Segue código de StringNodeParsingOption atualizado:

public class StringNodeParsingOption {
private boolean decodeStringNodes = false;

public void setDecodeStringNodes( boolean decodeStringNodes ){
this.decodeStringNodes = decodeStringNodes;
}

public boolean shouldDecodeStringNodes(){
return decodeStringNodes;
}

public static Node createStringNode(
StringBuffer textBuffer,
int textBegin,
int textEnd ){ /* NÃO HÁ MAIS A NECESSIDADE DO PARÂMETRO DE DECODE, FOI REMOVIDO */

if( decodeStringNodes ){
return new DecodingStringNode( textBuffer, textBegin, textEnd );
}
return StringNode( textBuffer, textBegin, textEnd );
}
}

 

Note que não há mais a necessidade de termos o parâmetro de entrada shouldDecode, pois o equivalente a essa variável é parte de StringNodeParsingOption, decodeStringNodes.

Atualizando a classe StringParser temos:

public class StringParser {
...
public Node find(...){
...

Node node = parser
.getStringNodeParsingOption()
.createStringNode(
textBuffer,
textBegin,
textEnd);

return node;
}
...
}

 

Para finalizar o passo cinco, vamos ao passo C dele, que nada mais nada menos será renomear a classe StringNodesParsingOption voltando para NodeFactory, incluindo a renomeação de seus métodos, setDecodeStringNodes() e shouldDecodeStringNodes().

Por que essa renomeação? Por que anteriormente não optamos por continuar com NodeFactory?

A renomeação é para passar maior legibilidade ao código, pois uma das vantagens dos padrões de projetos é que os programadores já entendem o tipo do código somente de saber pelo nome o padrão que está sendo utilizado, além do mais nossa nova classe é uma Factory, e o Factory no nome condiz com o que ela é, uma fábrica de objetos.

Já a não permanência em NodeFactory antes, no código, foi o que já explicamos: mover código de NodeFactory para StringNodesParsingOption implicaria, naquele momento, em menor esforço.

Segue atualização:

public class NodeFactory {
...
}

 

Agora a atualização em Parser:

public class Parser {
private NodeFactory nodeFactory = new NodeFactory();

public void setNodeFactory( NodeFactory nodeFactory ){
this.nodeFactory = nodeFactory;
}

public NodeFactory getNodeFactory(){
return nodeFactory;
}
...
}

 

E então a atualização em StringParser:

public class StringParser {
...
public Node find(...){
...

Node node = parser
.getNodeFactory()
.createStringNode(
textBuffer,
textBegin,
textEnd);

return node;
}
...
}

 

Com isso terminamos nosso exemplo utilizando o método de refatoração Mover Conhecimento de Criação para Factory.

Conclusão

Fique ciente que terminamos implementando a versão Simple Factory das possíveis três Factories. Mas todas as outras versões, como informado no início do artigo, são passíveis de serem utilizadas, incluindo até mesmo mais do que apenas uma na mesma refatoração.

Sabendo disso, alguns passos do método Mover Conhecimento de Criação para Factory podem mudar um pouco devido ao Factory que estiver sendo implementado.

Note também que quando utilizar o Factory, digo, qualquer uma das versões. Se houver apenas uma instância de classe de negócio sendo criada em uma outra classe do domínio do problema e você não souber se será ou não necessária a instanciação de mais classes dessa, opte por não colocar o código de criação em um Factory. Isso seria ineficiente, devido a quantidade de código movido para encapsular uma instanciação que é ainda única no sistema.

É importante notar que não disse para desistir de refatorar esse trecho de código, com instanciação direta. No caso seria bom estudar a possibilidade de utilizar outro método de refatoração, o Extrair Parâmetro, por exemplo. Pois assim você ainda estaria removendo código de criação direta das classes clientes.

Outros artigos da série

Abaixo listo todos os artigos da série refatoração de código já liberados:

Internalizar Singleton

Mover Embelezamento Para Decorator

Substituir Condicionais que Alteram Estado por State

Introduzir Objeto Nulo

Unificar Interfaces Com Adapter

Extrair Adapter

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 o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

Engenharia de Software: Código Limpo na PráticaEngenharia de Software: Código Limpo na PráticaDesenvolvimento Web
Refatoração Para PadrõesRefatoração Para PadrõesLivros
Facebook Login, Firebase Android - Parte 5Facebook Login, Firebase Android - Parte 5Android
Crash Reporting, Firebase Android - Parte 11Crash Reporting, Firebase Android - Parte 11Android

Compartilhar

Comentários Facebook

Comentários Blog (1)

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...
rodrigothread (1) (0)
16/06/2016, Quinta-feira, às 14h
ótimo, e vamos estudando!
Responder