16 de setembro de 2009

Design Patterns com Delphi : Bridge

Uma situação bastante comum quando se desenvolve programas complexos como um ERP é você ter um tipo de objeto associado a uma família de objetos de mesma interface e essa associação só ser resolvida em tempo de execução. Por exemplo, você tem objeto de negócio que pode ser exportado para arquivos em vários formatos mas o formato que vai ser usado de fato só é definido na última hora.

Se a classe desse objeto de negócio for base para outras classes, o nível de complexidade pode aumentar consideravelmente e tornar difícil a manutenção do código. Essa situação é abordada pelo Design Pattern estrutural Bridge, que estipula que se separe a abstração de uma funcionalidade de sua implementação. No exemplo citado, a abstração é a possiblidade de exportação dos dados do objeto enquanto são exemplos de implementação classes que exportem de fato para um formato, como XML, Excel ou CSV. Veja o diagram UML para esse Pattern.
Diagrama UML para Bridge

A nomenclatura para as classes participantes de uma solução do tipo Bridge é a seguinte :
A Abstraction é quem publica a interface para a funcionalidade que se está abstraindo, de modo que um cliente qualquer possa acessá-la sem se preocupar com detalhes da implementação. A abstração mantem internamente uma referência à implementação real (concreta) da funcionalidade. No diagrama acima, o papel de Abstraction é representado pela classe TWBusinessObj.
O RefinedAbstraction representa uma herança da Abstraction original. Como a implementação da funcionalidade é desacoplada da abstração, é possíver evoluir a abstração básica através de heranças de forma independente. A classe TWFatura no diagrama anterior é um exemplo desse desacoplamento.
O Implementor define a interface padrão para a implementação real da abstração. Esta interface pode ser completamente diferente daquela oferecida pela abstração a seus clientes, tornando possível reutilizar o Implementor em outras abstrações sem que uma interfira na outra. No exemplo, este papel cabe à classe TWExportable.
Cada ConcreteImplementor implementa uma solução específica para a interface publicada pelo Implementor, permitindo à abstração se beneficiar de novas implementações sem se que seja necessário modificá-la. No diagrama, TWExportableCSV e TWExportableXML ilustram esse papel.
O Client é uma classe externa que faz uso da abstração e, portanto, não tem conhecimento de como a abstração está de fato implementada. TWOperacao representa esta classe externa no diagrama acima.

Da mesma forma que no padrão Adapter, o Bridge também precisa estar associado a um pattern criacional como o Factory Method ou o Abstract Factory para resolver a questão de qual classe concreta deve ser instanciada em cada situação. Isso vale tanto para a Abstraction quanto para o Implementor.

Em Delphi, a implementação do Bridge usa herança e composição para criar as associações necessárias. Nas classes Implementor:
type
TWExportable = class
{ ... }
procedure InitExport;virtual;abstract;
procedure GravaCampo (pCampo : TWField);virtual;abstract;
procedure TermExport;virtual;abstract;
end;

TWExportableXML = class(TWExportable)
{ ... }
procedure InitExport;override;
procedure GravaCampo (pCampo : TWField);override;
procedure TermExport;override;
end;

TWExportableCSV = class(TWExportable)
{ ... }
procedure InitExport;override;
procedure GravaCampo (pCampo : TWField);override;
procedure TermExport;override;
end;

Em relação ao Abstraction, também há a hierarquia de classes com herança. Além disso, há a presença de uma propriedade com o Implementor, o que determina a composição.
type
TWBusinessObj = class
{ ... }
FImpl : TWExportable;
procedure Exporta;virtual;
end;

TWFatura = class(TWBusinessObj)
{ ... }
procedure Exporta;override;
end;

Por fim, a classe cliente TWOperacao pode fazer uso do método Exporta sem se preocupar com a implementação dele :
procedure TWOperacao.FazExportacao;
var obj : TWBusinessObj;
begin
{ ... }
obj := GetBusinessObjFromFactory (toFatura);
obj.Exporta;
end;

A diferença entre esse pattern e o pattern Adapter é sutil. No Adapter você tem uma classe preexistente e precisa ligá-la a outra classe que trabalha com uma interface diferente. O efeito é conseguido através da criação de uma nova classe que serve para mapear as chamadas a funções da classe preexistente para obter o resultado desejado. Como ambas as classes (a que mapeia as funções e a que tem as funções chamadas) são muito acopladas, ambas devem evoluir juntas. Ou seja, ao introduzir novas funcionalidades, ambas tem necessariamente que ser alteradas.

No caso do Bridge, as classes envolvidas são desacopladas e podem evoluir independentemente. Isto é, novas funcionalidades em qualquer uma delas não implica necessariamente em alterações em ambas. Portanto, no Bridge a abstração (o que se espera que uma classe faça) é separada da implementação (como a classe faz o que se espera dela).

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.