Padrões de Projeto em Delphi: Factory Method e Strategy com Boas Práticas
Introdução
Este artigo apresenta uma abordagem prática e detalhada sobre a aplicação dos padrões de projeto Factory Method e Strategy em Delphi, enfatizando princípios de Programação Orientada a Objetos (POO) e práticas de Clean Code. Forneço exemplos completos de código (unidades e uso) que compilam em versões modernas do Delphi, explicando decisões de projeto, gerenciamento de memória e como integrar esses padrões em aplicações reais. O foco é apresentar alternativas limpas, fáceis de testar e extensíveis, respeitando a regra de declarar métodos que serão sobrescritos como virtual na classe base e utilizando construtores/destrutores adequados.
Visão Geral dos Padrões
Os padrões de projeto ajudam a resolver problemas recorrentes de desenho de software. O Factory Method delega a criação de objetos a subclasses, permitindo escolher o tipo concreto em tempo de execução. O Strategy encapsula algoritmos intercambiáveis por meio de composição, facilitando trocar comportamento sem alterar a estrutura cliente.
Por que usar esses padrões em Delphi?
Delphi, sendo fortemente orientado a objetos, favorece a utilização de padrões para criar código mais testável, modular e de fácil manutenção. Ao aplicar Factory Method e Strategy você obtém desacoplamento e melhor aderência ao princípio Open/Closed (aberto para extensão, fechado para modificação).
Factory Method em Delphi
Nesta seção descrevo o padrão, a estrutura típica e um exemplo completo: uma fábrica de loggers que retorna diferentes implementações de TLogger. A classe base declara o método fábrica como virtual e as subclasses o override.
Estrutura e responsabilidades
A classe abstrata de fábrica define a assinatura do método criador (por exemplo, CreateLogger), enquanto subclasses concretas instanciam objetos concretos (p.ex., TConsoleLogger ou TFileLogger).
Exemplo de implementação (unidade: LoggerFactory.pas)
|
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
unit LoggerFactory; interface uses SysUtils, Classes; type // Classe base de logger: métodos que serão sobrescritos são abstract virtual TLogger = class public procedure Log(const AMsg: string); virtual; abstract; end; // Implementação concreta: console TConsoleLogger = class(TLogger) public procedure Log(const AMsg: string); override; end; // Implementação concreta: arquivo TFileLogger = class(TLogger) private FFileName: string; public constructor Create(const AFileName: string); destructor Destroy; override; procedure Log(const AMsg: string); override; end; // Fábrica abstrata: método fábrica declarado virtual TLoggerFactory = class public function CreateLogger: TLogger; virtual; abstract; end; // Fábricas concretas TConsoleLoggerFactory = class(TLoggerFactory) public function CreateLogger: TLogger; override; end; TFileLoggerFactory = class(TLoggerFactory) private FFileName: string; public constructor Create(const AFileName: string); destructor Destroy; override; function CreateLogger: TLogger; override; end; implementation { TConsoleLogger } procedure TConsoleLogger.Log(const AMsg: string); begin Writeln(Format('CONSOLE: %s', [AMsg])); end; { TFileLogger } constructor TFileLogger.Create(const AFileName: string); begin inherited Create; FFileName := AFileName; end; destructor TFileLogger.Destroy; begin inherited; end; procedure TFileLogger.Log(const AMsg: string); var SL: TStringList; begin SL := TStringList.Create; try if FileExists(FFileName) then SL.LoadFromFile(FFileName); SL.Add(AMsg); SL.SaveToFile(FFileName); finally SL.Free; end; end; { TConsoleLoggerFactory } function TConsoleLoggerFactory.CreateLogger: TLogger; begin Result := TConsoleLogger.Create; end; { TFileLoggerFactory } constructor TFileLoggerFactory.Create(const AFileName: string); begin inherited Create; FFileName := AFileName; end; destructor TFileLoggerFactory.Destroy; begin inherited; end; function TFileLoggerFactory.CreateLogger: TLogger; begin Result := TFileLogger.Create(FFileName); end; end. |
Exemplo de uso
Utilize a fábrica para obter o logger sem acoplar o código cliente às classes concretas:
|
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 31 |
var Factory: TLoggerFactory; Logger: TLogger; begin // Exemplo com console Factory := TConsoleLoggerFactory.Create; try Logger := Factory.CreateLogger; try Logger.Log('Teste no console'); finally Logger.Free; end; finally Factory.Free; end; // Exemplo com arquivo Factory := TFileLoggerFactory.Create('log.txt'); try Logger := Factory.CreateLogger; try Logger.Log('Teste em arquivo'); finally Logger.Free; end; finally Factory.Free; end; |
Strategy em Delphi
Strategy permite trocar algoritmos em tempo de execução. Implementaremos uma estratégia de compressão simples com uma versão sem compressão e um esqueleto para compressão real (integrável com bibliotecas de compressão).
Estrutura e responsabilidades
Uma classe abstrata define a interface do algoritmo (Compress), subclasses implementam diferentes algoritmos e um contexto (TCompressor) mantém e usa a estratégia atual.
Exemplo de implementação (unidade: CompressionStrategy.pas)
|
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
unit CompressionStrategy; interface uses SysUtils, Classes; type TCompressionStrategy = class public function Compress(const AData: TBytes): TBytes; virtual; abstract; end; // Estratégia sem compressão (pass-through) TNoCompressionStrategy = class(TCompressionStrategy) public function Compress(const AData: TBytes): TBytes; override; end; // Estratégia de compressão (esqueleto) TZCompressionStrategy = class(TCompressionStrategy) public function Compress(const AData: TBytes): TBytes; override; end; // Contexto que usa uma estratégia TCompressor = class private FStrategy: TCompressionStrategy; procedure SetStrategy(const Value: TCompressionStrategy); public constructor Create(AStrategy: TCompressionStrategy); destructor Destroy; override; function CompressData(const AData: TBytes): TBytes; property Strategy: TCompressionStrategy read FStrategy write SetStrategy; end; implementation { TNoCompressionStrategy } function TNoCompressionStrategy.Compress(const AData: TBytes): TBytes; begin Result := AData; end; { TZCompressionStrategy } function TZCompressionStrategy.Compress(const AData: TBytes): TBytes; begin // Implementação de exemplo: retorno vazio como placeholder. // Para compressão real, integre ZLib ou outras bibliotecas. SetLength(Result, 0); end; { TCompressor } constructor TCompressor.Create(AStrategy: TCompressionStrategy); begin inherited Create; FStrategy := AStrategy; end; destructor TCompressor.Destroy; begin FStrategy.Free; inherited; end; procedure TCompressor.SetStrategy(const Value: TCompressionStrategy); begin if FStrategy <> Value then begin FStrategy.Free; FStrategy := Value; end; end; function TCompressor.CompressData(const AData: TBytes): TBytes; begin if Assigned(FStrategy) then Result := FStrategy.Compress(AData) else Result := AData; end; end. |
Exemplo de uso
Crie o contexto com a estratégia desejada e altere em tempo de execução, se necessário:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var Compressor: TCompressor; Data, OutData: TBytes; begin Data := TEncoding.UTF8.GetBytes('Exemplo de dados'); Compressor := TCompressor.Create(TNoCompressionStrategy.Create); try OutData := Compressor.CompressData(Data); // Trocar para outra estratégia em tempo de execução Compressor.Strategy := TZCompressionStrategy.Create; OutData := Compressor.CompressData(Data); finally Compressor.Free; end; |
Boas práticas e observações
Seguindo Clean Code: escolha nomes claros (TLogger, CreateLogger, CompressData), mantenha funções curtas, evite efeitos colaterais e gerencie posse de memória. Em Delphi, padronize a posse (quem cria é responsável por liberar) e documente contratos de responsabilidade (quem libera objetos).
Testabilidade e extensão
Ambos os padrões facilitam injeção de dependência e testes unitários: você pode passar fábricas ou estratégias mockadas para o código cliente e validar comportamentos específicos sem alterar código de produção.
#Dica do Mestre
Se precisar de compressão real, integre a biblioteca adequada (por exemplo ZLib). ZLib não é o foco deste artigo, mas é uma biblioteca C amplamente usada para compressão: veja https://en.wikipedia.org/wiki/Zlib. Consulte também o DocWiki da Embarcadero para referências sobre bibliotecas do Delphi: https://docwiki.embarcadero.com/.
Recursos e referências
Recomendo a leitura de:
- DocWiki da Embarcadero: https://docwiki.embarcadero.com/ (documentação e exemplos oficiais).
- Clean Code — princípios e práticas de Robert C. Martin (aplicados aqui como orientação de nomes e simplicidade).
Conclusão
Factory Method e Strategy são padrões poderosos que, quando aplicados corretamente em Delphi, promovem um projeto desacoplado, extensível e testável. Este artigo apresentou implementações práticas e seguras, com atenção especial a declarações virtual/override, gerenciamento de memória e clareza de código. Comece integrando pequenas fábricas e estratégias em áreas críticas da sua aplicação e evolua conforme a necessidade.
Notas: exemplos escritos para compilação em versões modernas do Delphi. Ajustes mínimos podem ser necessários conforme a plataforma e a RTL. Pratique boas normas de codificação e siga as diretrizes locais do seu projeto.
