Design patterns (ou padrões de projeto) são estruturas e relacionamentos que utilizamos repetidas vezes em projetos orientados a objeto. Para conhecê-los pode ajudar você a projetar melhor (e com isso melhorar o desenvolvimento do seu software), tornar mais reutilizáveis de código e também ajudá-lo a aprender para a concepção de sistemas mais complexos.
Neste artigo, vamos falar especificamente de design patterns em Delphi, usando código para entender e mostrar os conceitos por trás de cada padrão.
Conteúdo
O que é Padrão de Projeto?
A aplicação de padrão de projeto pode melhorar a qualidade dos sistemas desenvolvidos, porque são, na prática, soluções testadas e aprovadas para problemas comuns dentro da programação.
Com o uso de padrões, você pode reduzir acoplamento, melhorar a legibilidade de seu código e aumentar o grau de reuso do mesmo. E também ajuda outros desenvolvedores a entender mais rápido o seu código.
Tipos de Padrão de Projeto
Segundo o GOF, os Design Patterns são divididos em três categorias:
- Criacionais – Abstract Factory, Factory Method, Builder, Prototype, Singleton;
- Estruturais – Composite, Adapter, Bridge, Decorator, Facade, Flyweight, Proxy;
- Comportamentais – Chain of Responsability, Strategy, Interpreter, Template Method, , Command, Iterator, Mediator, Memento, Observer, State, Visitor.
Como o Delphi te ajuda com padrões
O Delphi implementa totalmente linguagem orientada a objeto com a prática muitos refinamentos que simplificam o desenvolvimento.
A mais importante classe de atributos a partir de um padrão de perspectiva são os básicos de herança de classes; virtual e métodos abstratos; e a utilização da protegidas e âmbito público. Estas dar-lhe as ferramentas para criar padrões que podem ser reutilizados e estendidos, e permitem isolar funcionalidades variadas a partir da base de dados de atributos que são imutáveis.
Dica Extra: Treinamento exclusivo de Design Pattern para Delphi
Se você quer um método para aprender Design Pattern para Delphi, de um jeito prático, eu sugiro olhar este treinamento.
Exemplos de padrões de projeto
Aqui, nós vamos dar as definições sobre os principais padrões de projeto, junto com um exemplo.
Singleton
Garante que uma classe possuirá apenas uma instância, e proverá um único ponto de acesso global a mesma. Talvez seja um dos design patterns mais simples de serem implementados.
unit Singletn; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TCSingleton = class(TComponent) public constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; TOSingleton = class(TObject) public constructor Create; destructor Destroy; override; end; var Global_CSingleton: TCSingleton; Global_OSingleton: TOSingleton; procedure Register; implementation procedure Register; begin RegisterComponents('Design Patterns', [TCSingleton]); end; { TCSingleton } constructor TCSingleton.Create(AOwner: TComponent); begin if Global_CSingleton <> nil then {NB could show a message or raise a different exception here} Abort else begin inherited Create(AOwner); Global_CSingleton := Self; end; end; destructor TCSingleton.Destroy; begin if Global_CSingleton = Self then Global_CSingleton := nil; inherited Destroy; end; { TOSingleton } constructor TOSingleton.Create; begin if Global_OSingleton <> nil then {NB could show a message or raise a different exception here} Abort else Global_OSingleton := Self; end; destructor TOSingleton.Destroy; begin if Global_OSingleton = Self then Global_OSingleton := nil; inherited Destroy; end; procedure FreeGlobalObjects; far; begin if Global_CSingleton <> nil then Global_CSingleton.Free; if Global_OSingleton <> nil then Global_OSingleton.Free; end; begin AddExitProc(FreeGlobalObjects); end.
Adapter
Converte uma interface de uma classe em outra interface, de acordo com a exigência do cliente. Um adapter torna mais simples o uso do trabalho em conjunto e evita incompatibilidades.
unit Adapter; interface uses SysUtils, Classes; type { The new class } TNewCustomer = class private FCustomerID: Longint; FFirstName: string; FLastName: string; FDOB: TDateTime; protected function GetCustomerID: Longint; virtual; function GetFirstName: string; virtual; function GetLastName: string; virtual; function GetDOB: TDateTime; virtual; public constructor Create(CustID: Longint); virtual; property CustomerID: Longint read GetCustomerID; property FirstName: string read GetFirstName; property LastName: string read GetLastName; property DOB: TDateTime read GetDOB; end; { An interface method } { Lets us hide details of TOldCustomer from the client } function GetCustomer(CustomerID: Longint): TNewCustomer; implementation const Last_OldCustomer_At_Year_2000 = 15722; Last_OldCustomer_In_Database = 30000; { The new class } constructor TNewCustomer.Create(CustID: Longint); begin FCustomerID := CustID; FFirstName := 'A'; FLastName := 'New_Customer'; FDOB := Now; end; function TNewCustomer.GetCustomerID: Longint; begin Result := FCustomerID; end; function TNewCustomer.GetFirstName: string; begin Result := FFirstName; end; function TNewCustomer.GetLastName: string; begin Result := FLastName; end; function TNewCustomer.GetDOB: TDateTime; begin Result := FDOB; end; type { The old class } TOldDOB = record Day: 0..31; Month: 1..12; Year: 0..99; end; TOldCustomer = class FCustomerID: Integer; FName: string; FDOB: TOldDOB; public constructor Create(CustID: Integer); property CustomerID: Integer read FCustomerID; property Name: string read FName; property DOB: TOldDOB read FDOB; end; constructor TOldCustomer.Create(CustID: Integer); begin FCustomerID := CustomerID; FName := 'An Old_Customer'; with FDOB do begin Day := 1; Month := 1; Year := 1; end; end; type { The Adapter class } TAdaptedCustomer = class(TNewCustomer) private FOldCustomer: TOldCustomer; protected function GetCustomerID: Longint; override; function GetFirstName: string; override; function GetLastName: string; override; function GetDOB: TDateTime; override; public constructor Create(CustID: Longint); override; destructor Destroy; override; end; { The Adapter class } constructor TAdaptedCustomer.Create(CustID: Longint); begin inherited Create(CustID); FOldCustomer := TOldCustomer.Create(CustID); end; destructor TAdaptedCustomer.Destroy; begin FOldCustomer.Free; inherited Destroy; end; function TAdaptedCustomer.GetCustomerID: Longint; begin Result := FOldCustomer.CustomerID; end; function TAdaptedCustomer.GetFirstName: string; var SpacePos: integer; begin SpacePos := Pos(' ', FOldCustomer.Name); if SpacePos = 0 then Result := '' else Result := Copy(FOldCustomer.Name,1,SpacePos-1); end; function TAdaptedCustomer.GetLastName: string; var SpacePos: integer; begin SpacePos := Pos(' ', FOldCustomer.Name); if SpacePos = 0 then Result := FOldCustomer.Name else Result := Copy(FOldCustomer.Name,SpacePos+1,255); end; function TAdaptedCustomer.GetDOB: TDateTime; var FullYear: Word; begin if CustomerID > Last_OldCustomer_At_Year_2000 then FullYear := 2000 + FOldCustomer.DOB.Year else FullYear := 1900 + FOldCustomer.DOB.Year; Result := EncodeDate(FullYear, FOldCustomer.DOB.Month, FOldCustomer.DOB.Day); end; function GetCustomer(CustomerID: Longint): TNewCustomer; begin if CustomerID > Last_OldCustomer_In_Database then Result := TNewCustomer.Create(CustomerID) else Result := TAdaptedCustomer.Create(CustomerID) as TNewCustomer; end; end.
Template Method
Template Method na prática, é algoritmo cuja obrigatoriedade é seguir os passos internos.
Define um passo a passo de uma operação, delegando alguns passos para subclasses. Este design pattern permite que subclasses possam redefinir certos passos de um algoritmo, sem alterar sua estrutura.
Abstração é implementado em Delphi através de métodos virtuais.
Métodos diferentes de métodos virtuais pela classe-base, e permite não ter uma implementação. A classe filha é completamente responsável para a implementação de um método abstrato. Chamar um método abstrato que não tenha sido substituído irá resultar em um erro de tempo de execução.
Este exemplo mostra algumas muito simples, mas ilustra o princípio de adiar a implementação de uma subclasse.
unit Tpl_meth; interface type TAbstractTemplateClass = class(TObject) protected function Algorithm_StepA: Integer; virtual; abstract; function Algorithm_StepB: Integer; virtual; abstract; function Algorithm_StepC: Integer; virtual; abstract; public function Algorithm: Integer; end; TConcreteClassA = class(TAbstractTemplateClass) protected function Algorithm_StepA: Integer; override; function Algorithm_StepB: Integer; override; function Algorithm_StepC: Integer; override; end; TConcreteClassB = class(TAbstractTemplateClass) protected function Algorithm_StepA: Integer; override; function Algorithm_StepB: Integer; override; function Algorithm_StepC: Integer; override; end;
Builder
Separar a construção de um objeto complexo da sua representação, de modo que o mesmo processo de construção possa criar diferentes representações.
Um Builder é semelhante ao Abstract Factory.
A diferença que o construtor se refere a único objeto de diferentes classes concretas, mas contendo várias partes, considerando que o abstract factory permite criar todo famílias de classes concretas. Por exemplo, um construtor pode construir uma casa, casa ou escritório.
Você pode empregar diferentes construtor de uma casa de tijolo ou de madeira, mas a casa que você gostaria de dar-lhes instruções semelhantes sobre o tamanho e a forma da casa.
type TAbstractFormBuilder = class private FForm: TForm; procedure BuilderFormClose(Sender: TObject; var Action: TCloseAction); protected function GetForm: TForm; virtual; public procedure CreateForm(AOwner: TComponent); virtual; procedure CreateSpeedButton; virtual; abstract; procedure CreateEdit; virtual; abstract; procedure CreateLabel; virtual; abstract; property Form: TForm read GetForm; end; type TRedFormBuilder = class(TAbstractFormBuilder) private FNextLeft, FNextTop: Integer; public procedure CreateForm(AOwner: TComponent); override; procedure CreateSpeedButton; override; procedure CreateEdit; override; procedure CreateLabel; override; end; type TBlueFormBuilder = class(TAbstractFormBuilder) private FNextLeft, FNextTop: Integer; public procedure CreateForm(AOwner: TComponent); override; procedure CreateSpeedButton; override; procedure CreateEdit; override; procedure CreateLabel; override; end; At runtime the client application instructs one of the concrete classes to create parts using the public part creation procedures. The concrete builder instance is passed to the folliwing procedure: procedure TForm1.Create3ComponentFormUsingBuilder(ABuilder: TAbstractFormBuilder); var NewForm: TForm; begin with ABuilder do begin CreateForm(Application); CreateEdit; CreateSpeedButton; CreateLabel; NewForm := Form; if NewForm <> nil then NewForm.Show; end; end;
Abstract Factory
Fornece uma interface para criação de famílias de ou relacionadas com objetos dependentes sem especificar suas classes concretas.
Em tempo de execução, a nossa aplicação de cliente instancia o resumo de fábrica com um betão de classe e, em seguida, usa o resumo interface. Partes do aplicativo cliente que usa a fábrica não precisa saber qual a classe do betão é realmente em uso.
TOAbstractFactory = class(TObject) public constructor Create; destructor Destroy; override; { abstract widget constructors } function CreateSpeedButton(AOwner: TComponent): TSpeedButton; virtual; abstract; function CreateEdit(AOwner: TComponent): TEdit; virtual; abstract; function CreateLabel(AOwner: TComponent): TLabel; virtual; abstract; end; TORedFactory and TOBlueFactory override the abstract interface to support different widget styles. TORedFactory = class(TOAbstractFactory) public { concrete widget constructors } function CreateSpeedButton(AOwner: TComponent): TSpeedButton; override; function CreateEdit(AOwner: TComponent): TEdit; override; function CreateLabel(AOwner: TComponent): TLabel; override; end; TOBlueFactory = class(TOAbstractFactory) public { concrete widget constructors } function CreateSpeedButton(AOwner: TComponent): TSpeedButton; override; function CreateEdit(AOwner: TComponent): TEdit; override; function CreateLabel(AOwner: TComponent): TLabel; override; end;
Factory Method
Factory Method define uma interface para criar um objeto, mas deixar que as subclasses decidam qual classe instanciar. O padrão de projeto permite que uma classe delegue a instanciação para subclasses.
Desta forma, permite você instanciar objetos por meio de um método parametrizado, o que reduz sua complexidade.
Este modelo é útil quando você deseja encapsular a construção de uma classe e isolar o conhecimento da classe concreta da aplicação do cliente através de uma interface abstrata.
TRedSpeedButton = class(TSpeedButton) public constructor Create(AOwner: TComponent); override; end; constructor TRedSpeedButton.Create(AOwner: TComponent); begin inherited Create(AOwner); Font.Color := clRed; end; function TORedFactory.CreateSpeedButton(AOwner: TComponent): TSpeedButton; begin Result := TRedSpeedButton.Create(AOwner); end;
Referências bibliográficas: