Olá, pessoal, como vão?
Sabemos que, em uma arquitetura orientada a objetos, a criação (ou instanciação) de objetos é uma das atividades mais comuns, além de ser bastante frequente. Porém, embora seja tão trivial, muitas vezes criamos estes objetos em classes erradas e não sabemos!
O propósito do Design Pattern Creator é nos ajudar a identificar as classes devidamente responsáveis pela criação de cada objeto. Acompanhe!
Introdução
Não há segredo em criar objetos. Basta declarar, instanciar e usá-lo, não é? Oras, pensando assim, por que existiria um padrão de projeto dedicado exclusivamente à este conceito?
Para responder à essa pergunta, considere o pequeno código a seguir:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
uses Classe; var Objeto: TClasse; begin Objeto := TClasse.Create; try Objeto.Executar; finally Objeto.Free; end; end; |
Não há nada incorreto no código acima. No entanto, o meu objetivo é destacar uma linha do código que provavelmente passou despercebida: a seção uses.
Para que você possa criar um objeto de um determinado tipo, é necessário incluir a referência nessa seção, ou seja, a unit na qual a classe está declarada. A simples inclusão de uma referência pode parecer “inofensiva”, mas, caso essa referência seja utilizada em abundância ou em diferentes módulos (pacotes, projetos, bibliotecas, etc.), a arquitetura pode ser prejudicada em função do alto acoplamento.
Há três motivos que atestam essa afirmação:
- Quando uma unit que é utilizada em vários locais do projeto sofre uma alteração, ocorre um efeito conhecido como Dependência Magnética (que já comentei no artigo sobre DRY). Em suma, esse efeito consiste no impacto que será causado em todas as classes que utilizam essa unit e que, por consequência, terão que ser recompiladas;
- Quando se tem uma única unit que é referenciada em diversos pontos do código, provavelmente há um erro de abstração;
- A inclusão demasiada de units na seção uses afeta a performance da IDE, já que produz problemas de referência circular (já tive experiência com isso!).
Creator
Para evitar todos estes problemas, portanto, podemos empregar as diretrizes recomendadas pelo Design Pattern Creator para identificar os responsáveis ideais pela criação de objetos. Desse modo, o acoplamento da arquitetura é reduzido e haverá menos instanciações arbitrárias, além de uma arquitetura mais previsível. A intenção é deixar claro que a criação de objetos não deve ocorrer em qualquer classe. Deve existir um motivo.
Observe que, diferente dos outros padrões de projeto, o Creator traz orientações, e não uma solução de arquitetura. Para o Creator, uma classe X só pode criar objetos da classe Y caso um (ou mais) dos requisitos abaixo seja atendido.
1) Classe X compõe ou agrega instâncias da classe Y
Por exemplo, uma classe de pedidos (X) que agrega instâncias dos itens do pedido (Y):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
type TItemPedido = class private FCodProduto: integer; FQuantidade: integer; public property CodProduto: integer read FCodProduto write FCodProduto; property Quantidade: integer read FQuantidade write FQuantidade; end; TPedido = class private FItensPedido: TObjectList<TItemPedido>; public procedure AdicionarItem(const ACodProduto, AQuantidade: integer); end; { TPedido } procedure TPedido.AdicionarItem(const ACodProduto, AQuantidade: integer); var LItemPedido: TItemPedido; begin LItemPedido := TItemPedido.Create; LItemPedido.CodProduto := ACodProduto; LItemPedido.Quantidade := AQuantidade; FitensPedido.Add(LItemPedido); end; |
2) Classe X registra instâncias da classe Y
“Registrar”, neste contexto, se refere à persistência da instância (em um banco de dados, por exemplo), ou equivale simplesmente a “manter” uma instância em memória. No exemplo abaixo, usando o mesmo cenário do requisito anterior, a classe de pedidos (X) registra (mantém) uma instância da classe de clientes (Y).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
type TCliente = class public constructor Create(const ACodigo: integer); function GetEnderecoEntrega: string; end; TPedido = class private FCliente: TCliente; public procedure ConsultarCliente(const ACodCliente: integer); function GetEnderecoEntrega: string; end; implementation { TPedido } procedure TPedido.ConsultarCliente(const ACodCliente: integer); begin // Cria uma instância da classe TCliente correspondente ao código do parâmetro FCliente := TCliente.Create(ACodCliente); end; procedure TPedido.GetEnderecoEntrega(const ACodCliente: integer); begin // Delega a chamada para a instância da classe TCliente result := FCliente.GetEnderecoEntrega; end; |
Dessa forma, é possível obter o endereço de entrega do cliente com essa instrução:
1 |
LEnderecoEntrega := LPedido.GetEnderecoEntrega; |
Observe que também poderíamos obter o endereço de entrega como LPedido.Cliente.GetEnderecoEntrega
, mas, para isso, teríamos que expor uma propriedade para acessar o objeto FCliente
, violando o encapsulamento.
3) Classe X é “íntima” das instâncias da classe Y
Em outras palavras, a classe X deve ser “próxima” das instâncias da classe Y, ou seja, fazer parte do mesmo contexto. Na minha opinião, este é o requisito mais relevante, mas, ao mesmo tempo, é também o mais negligenciado.
No exemplo a seguir, a classe de transferência de arquivos (X) cria instâncias da classe TIdFTP
(Y), já que estão intimamente vinculadas na perspectiva de contexto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
type TTransferenciaArquivos = class public procedure TransferirArquivo(const AArquivo: string); end; implementation { TTransferenciaArquivos } procedure TTransferenciaArquivos.TransferirArquivo(const AArquivo: string); var IdFTP: TIdFTP; begin IdFTP := TIdFTP.Create(nil); try { ... } IdFTP.Put(AArquivo); finally IdFTP.Free; end; end; |
4) Classe X possui dados de inicialização da instância da classe Y
Se a classe X possui a maior parte ou todos os dados necessários para instanciar um objeto da classe Y, então a responsabilidade está correta. No código abaixo, a classe de contas a receber (X) preenche os atributos da classe de correção monetária (Y) pelo construtor antes de executar o método de cálculo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TContasReceber.CalcularCorrecaoMonetaria; var CorrecaoMonetaria: TCorrecaoMonetaria; begin try CorrecaoMonetaria := TCorrecaoMonetaria.Create (FDataVencimento, Date, FValor, (FPercentualJuros / 100)); CorrecaoMonetaria.ExecutarCalculo; finally CorrecaoMonetaria.Free; end; end; |
Outra forma de preencher os atributos é através de propriedades:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TContasReceber.CalcularCorrecaoMonetaria; var CorrecaoMonetaria: TCorrecaoMonetaria; begin try CorrecaoMonetaria := TCorrecaoMonetaria.Create; CorrecaoMonetaria.DataVencimento := FDataVencimento; CorrecaoMonetaria.DataCalculo := FDataCalculo; CorrecaoMonetaria.Valor := FValor; CorrecaoMonetaria.PercentualJuros := FPercentualJuros; CorrecaoMonetaria.ExecutarCalculo; finally CorrecaoMonetaria.Free; end; end; |
That’s It!
Leitores, caso nenhum dos requisitos acima seja atendido, recomendo a revisão da arquitetura. Existe uma grande possibilidade de uma classe estar criando objetos indevidamente. Neste caso, novas classes intermediárias devem ser criadas; algumas responsabilidades devem ser extraídas para classes específicas; ou a criação de objetos deve ser movida para métodos compartilhados.
Uma pequena exceção destes requisitos é o Design Pattern Factory, do GoF, uma vez que a sua responsabilidade é justamente instanciar objetos.
Na verdade, eu gostaria de detalhar um pouco mais sobre cada um destes requisitos, mas o artigo ficaria muito extenso.
Espero que tenham gostado, pessoal.
Até breve!
Bom dia André.
Ja desenvolvo um sistema pra nossa empresa familiar em VB com um banco de dados Access.
Queria migrar pra uma linguagem mais poderosa como o Delphi e gostaria de uma dica sua de como começar, qual BD usar, ambiente de desenvolvimento e se tem um programa simples pra download para que eu possa entender o início da linguagem, por exemplo, um sistema de login/senha para se conectar no sistema.
Parabéns pelo blog e obrigado.
Leandro.
Boa noite, Leandro! Desculpe-me pela demora.
Que bom que deseja migrar para o Delphi! A ferramenta está recebendo melhorias constantes e atualmente fornece uma série de recursos produtivos.
Leandro, ano passado a Embarcadero lançou o Delphi Community que pode ser usada gratuitamente por equipes de até 5 desenvolvedores e faturamento menor que 5 mil dólares anuais. Essa versão provavelmente irá lhe atender! Clique aqui para fazer um cadastro e baixá-la.
Sobre o banco de dados, há vários open-sources disponíveis na web. No seu caso, como é um sistema pequeno, eu recomendo o Firebird. É leve, rápido e simples de utilizar. Para administrar o banco de dados (por exemplo, criar tabelas), recomendo o IBExpert.
Infelizmente não tenho um programa simples de exemplo. Mesmo assim, para iniciantes na linguagem, eu sempre indico a playlist de vídeos abaixo do MVP Thulio Bittencourt, que inicia desde o conhecimento da IDE até utilização de componentes:
https://www.youtube.com/playlist?list=PLvrBgLo9icwMQTUVGMlT2436XO3MD5NEn
Grande abraço e boa sorte nessa nova jornada! 🙂
Esse blog é um verdadeiro bálsamo.
A gente deveria ler ele pelo menos uma vez por semana, pra se descontaminar dos maus hábitos e fugir das gambiárras!
Obrigado por compartilhar!
Jean, muito obrigado pelo feedback!
São comentários como o seu que me motivam a continuar este trabalho.
Eu volto em breve com novos artigos!
Abração!
Bacana seus artigos, parabéns! Agora, cá pra nós, Delphi Community é brincadeira… 5 mil / 12 meses / 5 devs = 84 dólares por mês!!! Você, como MVP, já deu um feedback pra Embarcadero como é ridícula essa política comercial deles? Por isso o Delphi é essa ferramenta que todos pensam que já morreu. Abraços!
Olá, Dener!
Vou entrar em contato com você.
Abraço!
Parabéns André. Seu conteúdo é incrível.
Muito obrigado, Willian!