Inversão de controle Parte 2: Factory (Fábricas) – Blog do FT
Inversão de controle Parte 2: Factory (Fábricas)
7 de julho de 2019 10 de fevereiro de 2020 Francisco Thiago
Olá! Tudo bem? Este é o segundo artigo de uma série sobre inversão de controle. No primeiro artigo, abordamos aspectos conceituais desta técnica. Neste artigo, vamos nos aprofundar no padrão de projeto Factory e em como ele pode contribuir para a Inversão de Controle.
Mas primeiro, uma dica!
O nome Kent Beck reside no panteão dos grandes escritores de arquitetura e engenharia de software. Entre as várias obras dele, existe uma que eu aconselho a sua leitura: Padrões de Implementação – um catálogo de padrões indispensáveis para o dia a dia do programador. Esse livro é praticamente uma obra que te ensina a programar. Não estou falando que ele vai te ensinar a sintaxe de uma linguagem específica (ainda que ele utilize java como exemplo). Ele vai te ensinar todas as artimanhas para ser um bom programador e escrever código de qualidade. Sem dúvida é um complemento importante para o conhecimento adquirido após a leitura do Clean Code (MARTIN, 2008).
Beck reserva o capítulo 7 para falar sobre comportamento do software. E imagine qual o assunto que ele aborda? O fluxo do software! Entrar em maiores detalhes iria fugir do escopo desse artigo. Então, se você é daqueles que acredita que inversão de controle só é possível por meio de frameworks, dê uma olhada em como o Kent Beck constrói a inversão apenas definindo como as mensagens entre os objetos são executadas.
Factory: O que são? Pra quê servem? Como são?
O catálogo de padrões de projeto mais famoso, com certeza, é o trabalho do GoF: Desing Patterns. Bastante detalhista, o livro aborda vinte e três padrões de projeto, explicando sua intenção, motivação, aplicabilidade, estrutura, seus impactos no projeto e sua mecânica de implementação.
Um dos conjuntos de padrões abordados, e que interessam para nós neste momento, são os padrões de criação. Os padrões desse conjunto se ocupam em encapsular a lógica de criação dos objetos, tornando o sistema independente da forma como os seus objetos são instanciados. Os cinco padrões de criação abordados são: Abstract Factory, Builder, Factory Method, Prototype e Singleton. Como você já deve ter imaginado, vamos falar apenas dos padrões Abstract Factory e Factory Method.
Você vai utilizar o padrão Abstract Factory quando quiser garantir que apenas objetos de uma mesma família sejam sempre criados. Não entendeu? Talvez com um exemplo fique um pouco melhor:
O desafio é criar um conjunto de classes responsável por abstrair o processo de escrita de SQL. Selects básicos são fáceis de implementar. Dificilmente um banco fugiria do padrão ANSI. No entanto, quando é necessário implementar pesquisas mais específicas como "os primeiros X registros, pulando Y registros", cada banco possui uma implementação diferente. Como proceder?
retorno = "top {0}";
retorno = "limit {0}";
retorno = "rownum < {0}";
retorno = "first {0}";
O problema de um código como esse é a escalabilidade. No momento em que um novo banco de dados tiver de ser suportado, você terá que varrer o sistema em busca das chamadas específicas para cada banco e então acrescentar o novo código.
Outro ponto importante a ser lembrado é que a classe que for montar o nosso select precisa saber exatamente onde colocar o código retornado. Isto porque o SQLServer e o Firebird montam a sentença com Top e Limit, respectivamente, no início do select. Já o Oracle, por exemplo, coloca o código no final da expressão. O desenvolvedor ou desenvolvedora terá que tomar muito cuidado para não misturar o código entre os bancos. Basta esquecer uma variável e pronto: nada funciona.
Isto é uma Factory?
Na imagem, nós temos uma classe Client. A sua implementação não nos importa. Ela está ali apenas para sinalizar a entidade de código que está utilizando a nossa classe. A classe Client solicita implementações de AbstractProductAe AbstractProductB. Estas são interfaces que abstraem os produtos concretos ProductA1, ProductA2, ProductB1 e ProductB2. Estes produtos, voltando ao nosso exemplo de criação de SQL, poderiam ser as classes responsáveis por gerar o código dos "retorne os primeiros X registros" (product) específicos de cada banco (A1 e A2).
As implementações são solicitadas para a Abstract Factory. Na imagem ela está representada como uma interface. Esta interface precisa possuir os métodos que retornem os produtos esperados. Seria algo como:
Acontece que o banco de dados precisa ser informado em algum ponto. Neste caso, o padrão prevê a criação de uma classe que abstraia a escolha das fábricas: A AbstractFactory. Mesmo com esse nome, não estamos falando de uma classe abstrata. Ela é concreta (ou seja, você deve instanciá-la diretamente). O que a torna "abstrata" é justamente o fato de que ela não retorna os produtos: ela delega a criação dos objetos para outras fabricas, que na imagem são as ConcreteFactory1 e ConcreteFactory2. Estas classes também implementam a interface AbstractFactory. Mas ao invés de delegarem a criação de objetos para outra classe, elas mesmas fazem a criação.
Não é muito difícil, não é mesmo? Vamos conhecer melhor agora o padrão Factory method.
Console.WriteLine("Sending by drone");
Console.WriteLine("Mix with {0}", ingredient.GetType().Name);
Como você pode perceber, todo o algoritmo está definido na classe abstrata Pizza. Ela sabe quase tudo, exceto qual é a receita. O método responsável por juntar os ingredientes é CatchIngredients(), que será implementado pela classe filha. Vamos fazer uma pizza de pepperoni?
Desculpe. Eu realmente não sei fazer pizza. Mas faça de conta que em uma pizza de pepperoni vai… Pepperoni, presunto e muzzarela. Se eu quiser criar uma pizza de pepperoni, basta declarar uma variável do tipo Pizza e instanciar um objeto filho. Pepperoni, no caso:
Quando vou utilizar o Desing Pattern Factory em Inversão de Controle?
No nosso exemplo nós informamos a AbstractFactory qual o tipo de banco de dados deve ser utilizado. Ao invés disso, a classe poderia receber essa informação a partir de um arquivo de configuração. Ou seja, por mais que alguém alegue que, com as factories, estejamos apenas transferindo o fluxo para outro lugar, o elemento "arquivo de configuração" retira o controle do fluxo da aplicação e o toma para si. O que certamente inverte o controle da aplicação – que muda em tempo de execução.
Em um framework de IoC as factories também podem exercer um papel importante. Elas sabem como criar objetos, podendo até transferir esta responsabilidade builders, que por sua vez podem adicionar decorators ao produto final. Tudo isso ficando completamente invisível para o cliente, que apenas pede seus objetos criados. Essa transparência permite que as aplicações sejam completamente alteradas em tempo de execução. Alterando apenas um parâmetro.
Uma última palavra sobre Factory e Design Patterns
Quando começamos a estudar padrões de projetos, somos levados pela crença de que devemos implementar o padrão completamente. Igual manda a receita. Ou estaremos fazendo errado. Isso nem sempre é verdade.
Os padrões servem para ajudar a solucionar problemas. E não para acrescentar complexidade desnecessária. Eu tenho certeza que você já deve ter feito algo parecido com o Factory Method, mesmo antes de conhecê-lo. Isto porque os padrões são intuitivos. O catálogo do GoF surgiu à partir de código real. Eles apenas catalogaram soluções que já existiam. E não o contrário. Desta forma, seu papel principal na mesa do/da dev é de servir como forma de inspiração e mapa para quem está perdido.
Particularmente aconselho que você, iniciante, siga o catálogo ao pé da letra. Contudo, conforme for se familiarizando com eles, implemente modificações que façam sentido. Se você tem uma classe com métodos estáticos que retornam instâncias, você pode chamá-la de fábrica sim. Se você está substituindo a sobrecarga de construtores por métodos estáticos de criação, você está implementando um método-fábrica também.
Por hoje é só. No próximo artigo vamos conhecer o padrão Observer.
Inversão de controle Parte 1: Um pouco de teoria
arquitetura design patterns Desing de Software DIP Factory Factory Method Injeção de dependência Inversão de controle IoC