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: