25 de setembro de 2009

Design Patterns com Delphi : Composite

Muitas situações do mundo real podem ser modeladas num sistema computacional como uma estrutura de objetos em forma de árvore, num relacionamento todo-partes. Isto é, uma classe de objetos é formada por outras classes (e estas classes também podem ser compostas por outras), configurando uma hierarquia em árvore entre os objetos envolvidos.

Às vezes, nesse tipo de hierarquia há certas operações aplicáveis ao objeto "todo" que podem também fazer sentido se aplicadas às partes isoladamente. Um exemplo clássico desse cenário é ter um objeto Desenho composto por elementos gráficos como linhas, círculos, triângulos, etc. O Desenho terá operações como "pintar-se", "redimensionar-se" e "mover-se" e estas mesmas operações podem ser aplicadas a cada uma das partes de forma independente.

O objetivo do Design Pattern estrutural Composite é justamente esse : permitir que classes Clientes possam tratar objetos individuais ou composições desse objeto de forma uniforme, podendo aplicar operações no todo ou em partes dele sem se preocupar se o objeto é de um tipo ou de outro. Um exemplo aplicável num ERP como o da ABC71 é a ideia de contratos. Os Contratos são compostos por Cláusulas e tanto o Contrato quanto suas Cláusulas individuais podem ser alteradas, revogadas ou perder validade.

Veja o diagram UML para esse Pattern.
Diagrama UML para Composite

A nomenclatura para as classes participantes de uma solução do tipo Composite é a seguinte :
O Component é quem declara a interface comum a todos os objetos da composição. Ele também implementa a parte dessa interface que fizer sentido para todos os objetos da composição, isto é, o Component não precisa ser uma classe abstrata pura. A interface ainda introduz métodos para gerenciar filhos, isto é, a coleção de partes que compõem o todo. No diagrama acima, esse papel cabe à classe TWClausulaContrato.
O Composite propriamente dito implementa todo o comportamento relacionado aos Components que têm filhos, permitindo gerenciar a lista de filhos e introduzindo outras operações que façam sentido nesse contexto. No exemplo, quem exerce esse papel é a classe TWContrato, que poderá ter uma lista de cláusulas como filhos.
Os Components que são elementos primitivos, isto é, não têm filhos, são denominados Leaf (folhas). No caso dos contratos, uma cláusula (classe TWClausula) é um elemento folha pois não é permitido a ele ser composto por outras cláusulas. Note que da forma como está projetado, um contrato pode ser composto por outros contratos e cláusulas. Assim, posso ter modelos de contratos com cláusulas específicas e moldar o contrato principal como uma coleção desses modelos, tendo ainda a flexibilidade de adicionar cláusulas avulsas.
Por fim, o Client é qualquer parte da aplicação que faça chamadas a operações do Component. No diagrama, esse é o papel da classe TWOperação.

O mapeamento desse padrão usando Delphi é feito através de heranças simples. Para facilitar o tratamento de lista na classe Composite, pode-se usar internamente a própria classe TList da VCL.
type
TWClausulaContrato = class
{ ... }
procedure Cancelar;virtual;
procedure Aplicar;virtual;
function EstaValido : boolean;virtual;

procedure Add (AElem : TWClausulaContrato);virtual;abstract;
procedure Remove (AElem : TWClausulaContrato);virtual;abstract;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;virtual;abstract;
end;

TWClausula = class(TWClausulaContrato)
{ ... }
procedure Cancelar;override;
procedure Aplicar;override;
function EstaValido : boolean;override;

procedure Add (AElem : TWClausulaContrato);override;
procedure Remove (AElem : TWClausulaContrato);override;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;override;
end;

TWContrato = class(TWClausulaContrato)
{ ... }
_Clausulas : TList;
procedure Cancelar;override;
procedure Aplicar;override;
function EstaValido : boolean;override;

procedure Add (AElem : TWClausulaContrato);override;
procedure Remove (AElem : TWClausulaContrato);override;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;override;
end;

Para a classe TWClausula, as funções de gerenciamento não fazem nada - embora seja comum classes Leaf reportarem mensagens de erro para reforçar que elas não comportam filhos.
procedure TWClausula.Add (AElem : TWClausulaContrato);
begin
end;

procedure TWClausula.Remove (AElem : TWClausulaContrato);
begin
end;

function TWClausula.ItemAt ( AIndex : Integer) : TWClausulaContrato;
Result := Nil;
end;

Para a classe TWContrato, uso o TList para armazenar os filhos, sem importar se o novo elemento é uma cláusula simples ou um contrato mais complexo.
procedure TWContrato.Add (AElem : TWClausulaContrato);
begin
_Clausulas.Add (AElem);
end;

procedure TWContrato.Remove (AElem : TWClausulaContrato);
begin
_Clausulas.Remove (AElem);
end;

function TWContrato.ItemAt ( AIndex : Integer) : TWClausulaContrato;
Result := TWClausulaContrato(_Clausulas.Items[AIndex]);
end;


Mais Informações
Posts sobre Design Patterns

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.