O tema dos padrões de design é bastante popular. Muitos vídeos foram filmados e artigos foram escritos. Combina todos esses materiais com um "antipadrão". Complexidade acidental. Como resultado, os exemplos são obscuros, a descrição é confusa, como aplicar não é clara. E a principal tarefa dos padrões de design - simplificação (de código e trabalho em geral) não é alcançada. Afinal, o uso do modelo requer um esforço adicional. Aproximadamente o mesmo que o teste de unidade.
Vou tentar explicar os padrões de design em termos de como aplicá-los, onde e por que.
Seis geradores podem ser atribuídos a:
- Protótipo
- Fábrica abstrata,
- Método de fábrica
- Construtor
- Singleton
- Inicialização lenta.
Todos os outros padrões que se relacionam com geradores são um caso especial de aplicação e não há sentido em nos deter sobre eles.
Os padrões geradores podem ser divididos em três grupos, na pergunta a que eles respondem. Então, três perguntas:
Onde
Três padrões respondem a essa pergunta: Protótipo, Fábrica abstrata e Método de fábrica.
Um pouco sobre os termosDentro da estrutura do conceito de POO, existem apenas três locais em que teoricamente é possível gerar uma nova instância.
- Produto é a classe que é instanciada.
- Cliente é a classe que usará a instância instanciada.
- Parceiro - qualquer terceira classe no campo de visibilidade do Cliente.
Na verdade, esses padrões determinam o local da geração. Além disso, eles estão conectados hierarquicamente e têm um escopo diferente. A conexão é mostrada na figura, as setas determinam a direção das chamadas.

Na sua implementação, o “Método de Fábrica” pode delegar a geração de uma instância para a “Fábrica” existente ou para o “Protótipo”. O "protótipo", no entanto, não deve depender de ninguém e fazer tudo sozinho. Agora com mais detalhes.
"Protótipo"
Este modelo corresponde ao local "Produto", de fato, é o construtor da classe. Assim, uma instância de uma classe específica (anteriormente conhecida) é sempre gerada.
Dentro da estrutura deste modelo, o construtor conhece apenas os parâmetros passados diretamente a ele (o número de parâmetros tende ao número de campos da classe). Obviamente, há acesso total a todos os campos e propriedades da classe criada.
Os métodos adequadamente implementados do "Protótipo" permitem livrar-se de métodos adicionais de inicialização em público. Por sua vez, a interface externa da classe se torna mais fácil e menos tentadora de usar a classe para outros fins.
O que este modelo nos fornece:
- Baixa conectividade - a classe apenas conhece a si mesma, não depende de dados externos;
- Extensibilidade - os construtores podem ser redefinidos ou adicionados aos descendentes;
Dos menos:
- Para classes complexas, pode ser necessário passar muitos parâmetros para inicialização. Embora exista uma solução trivial.
- O uso direto no Cliente pode prejudicar a legibilidade e praticamente bloquear a possibilidade de substituir o tipo de instância que está sendo gerada no descendente do Cliente.
O modelo mais popular. Todo mundo usa, mas poucos sabem o que usa. É bom até que o primeiro modelo de trabalho seja obtido, até que as classes e suas relações sejam completamente definidas. Depois disso, o processamento e o aumento da abstração são obrigatórios.
"Fábrica abstrata"
Alguma classe Parceira. Pode ser especializado, ou "combinar". Pode ser estático (sem instância). Um exemplo de "combinação" pode ser uma classe de configuração. Também pode estar escondido atrás da fachada.
"Fábrica" geralmente vê todas as configurações globais do aplicativo (ou um subsistema separado). A geração imediata pode ser delegada ao Protótipo. Ao mesmo tempo, o número de parâmetros de entrada no método Factory será menor que em um construtor Prototype similar. A fábrica não decide quem criar com base nos parâmetros recebidos.
Este modelo é muito conveniente e fácil de implementar, mas requer um design preliminar. Se você criar fábricas para tudo, isso complicará o código. De fato, obtemos um análogo do Prototype, mas passamos para uma classe de terceiros.
Dos profissionais:
- Boa redefinição de descendentes
- Chamada simplificada
- Com base na fábrica, é fácil implementar uma substituição (modelo de estado)
Mas também há desvantagens:
- Requer design, especialmente para fábricas universais (que são usadas em muitos projetos). Em outras palavras, obter uma boa fábrica imediatamente não é fácil.
- É muito fácil estragar o código, existem duas áreas principais:
- Deslizando em direção ao Protótipo, mas em uma classe externa. Os métodos estão sobrecarregados com parâmetros; existem muitos métodos em si. Como resultado, a herança é difícil, tanto na própria fábrica quanto no cliente.
- Fábrica com método universal. Este método retorna qualquer instância, dependendo dos parâmetros passados. O resultado, como no primeiro caso.
Muito popular. Este modelo é usado por quem participou do curso GoF. Como regra, o código se torna ainda pior do que "antes de aplicar os modelos".
Faz sentido quando Fábricas aparecem durante o primeiro retrabalho do código. Nesse estágio, as combinações de parâmetros para as instâncias criadas já são conhecidas e não será difícil escrever métodos generalizados de Factory. Como resultado, as chamadas no cliente serão simplificadas.
Em alguns casos, é conveniente ocultar fábricas atrás da fachada. Por exemplo, o aplicativo possui uma dúzia de suas fábricas e uma dúzia de bibliotecas. Para eles, você pode construir uma fachada. Isso permitirá não vincular bibliotecas a cada módulo e também é fácil substituir uma fábrica por outra.
Método de fábrica
O topo da abstração em padrões generativos. Local de origem do cliente. A classe em que cada produto é colocado no Método de Fábrica tem todas as chances de uma vida longa. Se sem fanatismo, o eixo de desenvolvimento assumido deve necessariamente ser baseado neste modelo.
O método de fábrica não enxerga além de sua classe. O número de parâmetros transmitidos diretamente deve ser mínimo (no limite de zero). O próprio método deve ser construído levando em consideração a possibilidade de sobreposição no descendente.
Um erro comum é a inicialização complicada em um método. Por exemplo, ao criar uma instância complexa (o modelo Builder), a criação de todas as partes do objeto futuro é colocada em um método. Como resultado, é difícil sobrepor esse método no descendente.
Dos profissionais:
- Será fácil combinar o método do modelo
- Temos um código conciso no qual a lógica é claramente visível (ela não precisa ser vista entre vários métodos e parâmetros)
Não há essencialmente contras.
Este modelo quase nunca é usado. Como regra, isso só pode ser visto em projetos com profunda elaboração preliminar. Ideal quando o Método de Fábrica delega a geração para "Fábrica" ou "Protótipo".
Pequeno exemplo
Temos uma classe para fazer logon em um arquivo no disco rígido. É assim que os métodos genéricos dentro dos padrões "Onde?" Podem parecer:
Protótipo:
constructor Create(aFilename: string; aLogLevel: TLogLevel);
Tudo o que o designer deve saber é passado para ele na forma de parâmetros.
Fábrica:
function GetLogger(aLogLevel: TLogLevel): ILogger;
A fábrica sabe em qual arquivo gravar, conforme especificado nas configurações do aplicativo.
Método de fábrica:
function NewLogger: ILogger;
Na classe Client, é conhecido com que detalhes registrar.
Nesse design, para substituir a classe de log por um stub, basta redefinir o NewLogger no descendente do cliente. Isso é útil ao realizar testes de unidade.
Para fazer logon no banco de dados, basta substituir o método GetLogger no descendente do Factory.