C#
- Padrões Estruturais
Gof - Bridge
![]() |
Neste artigo vou apresentar com o padrão estrutural Gof Bridge. |
O padrão estrutural Bridge desacopla uma abstração de sua implementação para que as duas possam variar de forma independente.
Para isso ele atua de forma a dividir a lógica de negócio (uma classe maior) em hierarquias de classe separadas que podem ser desenvolvidas independentemente. Assim, o objetivo principal do padrão Bridge é separar a interface do objeto de sua implementação.
O padrão Bridge permite que a abstração e a implementação sejam desenvolvidas de forma independente e o código do cliente pode acessar apenas a parte da abstração sem se preocupar com a parte da implementação. Este padrão separa a hierarquia de abstração e a hierarquia de implementação em duas camadas diferentes para que a mudança em uma hierarquia não afete o desenvolvimento ou a funcionalidade de outra hierarquia.
Como exemplo de aplicação do Bridge temos o seguinte cenário :
Suponha que temos em um projeto um requisito para salvar ou excluir um objeto na persistência. Aqui, podemos salvar o objeto em um sistema de arquivos ou em um banco de dados.
A seguir, temos dois implementadores que são usados para aplicar a
persistência :
Portanto, podemos usar qualquer um dos implementadores acima para salvar o objeto.
De acordo com o
padrão Bridge, a abstração e a implementação devem estar em uma camada separada.
Aqui, a persistência é a camada de abstração e a implementação da persistência é
a camada de implementação.
Neste cenário se adicionarmos uma nova implementação ou se removermos qualquer
implementação, isso não afetará a camada de abstração.
Esta é a vantagem
do padrão Bridge : A camada de persistência abstrata usará qualquer um
dos implementadores para salvar ou excluir um objeto e o cliente usará
apenas a abstração para salvar ou excluir o objeto.
Temos assim o desacoplamento da abstração de sua implementação e ambas
podem evoluir de forma independente.
Diagrama UML
A seguir temos o diagrama UML do padrão Bridge segundo o Gof:
Os participantes são:
1-
Abstraction - É
uma classe abstrata e contém membros que definem um objeto de negócio abstrato e
sua funcionalidade.
Ela contém uma referência a um objeto do tipo Bridge
(representado aqui por Implementor). Ela
também pode atuar como a classe base para outras abstrações.
Esta é uma interface que é usada pelo cliente para atingir sua funcionalidade/solicitação.
2- RefinedAbstraction - É uma classe que
herda da classe Abstraction ou estende a interface
definida pela em Abstraction.
3- Implementor(Bridger) - É uma interface
que atua como uma ponte (Bridge) entre a classe de abstração e as classes do
implementador e também torna a funcionalidade da classe do implementador
independente da classe de abstração,e também define a interface para a
implementação das classes concretas
O
Adapter implementa a interface
Target e é composto com o Adpatee. Ela é
responsável pela comunicação entre o Client e a
Adaptee.
4- ConcreteImplementorA e ConcreteImplementorB -
São classes que implementam Implementor ou seja a
interface Bridge e também fornecem os detalhes de
implementação para a classe Abstraction associada.
Podemos verificar no diagrama as duas hierarquia de classes que são definidas :
a abstração e a implementação, de forma desacoplada e que podem
evoluir de forma independente.
Usos do padrão Bridge
Podemos usar o padrão Bridge quando :
Vantagens e desvantagens do padrão Bridge
As vantagens em usar o padrão são:
Vale lembra que aplicando o adapter geralmente se usa o principio da responsabilidade única na separação da conversão da interface e o principio OCP/Aberto-Fechado - introduzido novos tipos de adaptadores sem quebrar o código.
Como desvantagens do padrão Bridger podemos destacar que a utilização do padrão pode aumentar a complexidade do código pela criação de uma hierarquia de classes complexa.
Além disso você tem que considerar que o código que está sendo usado para abstração e implementação deve ser separável, e, a funcionalidade separada deve ser capaz de funcionar de forma independente e isso pode levar a uma complexidade no código.
Aplicando o padrão Bridge
Veremos agora um exemplo prático de aplicação do padrão Bridge.
Vamos supor que
você precisa processar o salário dos funcionários de uma empresa, e, você
precisa salvar os dados nos formatos XML ou JSON.
O processo de salvar em um formato específico será decidido em tempo de
execução, e, o cliente pode incluir outros formatos para geração do arquivo como
formato texto, pdf , etc.... Além disso o processamento do salário também
poderá variar no futuro:
Vemos aqui o
cenário típico para aplicar o padrão Bridge,
e assim vamos definir uma abstração para calcular o salário
e gerar os dados nos formatos XML e JSON, e definir
a implementação para efetivamente gerar os dados no formato XML e JSON.
Usando o padrão Bridge vamos separar a abstração da implementação de forma que
elas possam evoluir de forma independente. Assim se eu precisar modificar
o cálculo do salário eu não preciso alterar a implementação, e, seu precisar
alterar a implementação. incluindo um novo formato para gerar o arquivo. eu não
preciso mexer na abstração, e com isso temos um código desacoplado.
Vamos então criar um projeto Console usando o .NET 5.0 e a linguagem C# e a seguir vamos criar a classe Funcionario que representa o nosso modelo de domínio:
public class Funcionario { public int Id { get; set; } public string Nome { get; set; } public decimal SalarioBase { get; set; } public decimal Incentivo { get; set; } public decimal SalarioTotal { get; set; } } |
A seguir vamos criar a interface IGeraArquivo que representa o Implementor ou seja é o nosso Bridge, onde vamos definir um contrato para gravar o arquivo no formato especificado para o funcionário:
using Bridge1.Domain;
public interface IGeraArquivo |
A seguir vamos
criar as classes concretas GeraJson e GeraXML que
representam ConcreteImplementor e implementam a
interface IGeraArquivo e grava os dados do salário
do funcionário nos formatos XML e JSON:
1- GeraXML()
using Bridge1.Domain; using System; using System.IO; using System.Xml.Serialization;
public class GeraXML : IGeraArquivo public void GravaArquivo(Funcionario funcionario) xmlSerializer.Serialize(fileStream, funcionario); Console.WriteLine($"Salário para o Funcionário : {funcionario.Nome} " + |
2- GeraJSON()
using Bridge1.Domain; using Bridge1.Implementor; using System; using System.IO; using System.Text.Json; public class GeraJson : IGeraArquivo public void GravaArquivo(Funcionario funcionario) File.WriteAllText(nomeArquivo, funcionarioSerializado); Console.WriteLine($"Salário para o Funcionário : {funcionario.Nome} " + |
Acima temos as duas classes concretas que geram os arquivos nos formatos especificados.
Agora vamos criar a classe AbstrationGeraArquivo que representa o Abstration e é uma classe abstrata onde temos uma instância de IGeraArquivo e onde é usada esta instancia para gerar o arquivo no formato especificado :
using Bridge1.Domain; public abstract class AbstrationGeraArquivo { protected IGeraArquivo geraArquivo;
protected AbstrationGeraArquivo(IGeraArquivo geraArquivo) public virtual void GravaArquivo(Funcionario funcionario) |
A seguir crie a classe CalculaSalario que representa o RefinedAbstration. Esta classe herda de Abstration e recebe no construtor um objeto IGeraArquivo em tempo de execução calcula o salário do funcionário internamente e executa a operação para persistir os dados do funcionário no formato do arquivo especificado usando a instancia recebido de IGeraArquivo :
using Bridge1.Abstration; using Bridge1.Domain; using Bridge1.Implementor; using System; public class CalculaSalario : AbstrationGeraArquivo public void ProcessaSalarioFuncionario(Funcionario funcionario) funcionario.SalarioTotal = funcionario.SalarioBase + funcionario.Incentivo; Console.WriteLine($"Valor do Salário Total para o funcionario {funcionario.Id} \n" + geraArquivo.GravaArquivo(funcionario); |
Na classe Program vamos aplicar a utilização desta implementação:
using Bridge1.ConcreteImplementor; using Bridge1.Domain; using Bridge1.RefinedAbstration; using System; namespace Bridge1 //define os dados do funcionário //processa o salario //altera o valor do incentivo em tempo de execução //altera o formato para gerar o arquivo em tempo de execução calculaSalario.ProcessaSalarioFuncionario(funcionario); Console.ReadKey(); |
Executando o projeto teremos o resultado:
Note que o padrão
Bridge tem quase a mesma estrutura do padrão
Adapter. A diferença é que ele é usado ao
projetar novos sistemas em vez do padrão Adapter
que é usado com sistemas já existentes.
Assim o padrão Bridge pode ser usado quando uma
nova versão de um software ou sistema é lançada, mas a versão mais antiga do
software ainda está em execução para seu cliente existente. Com isso não
existe a necessidade de alterar o código do cliente, mas o cliente precisa
escolher qual versão deseja usar.
Pegue o código
do projeto aqui :
Padrao_Bridge.zip
"Brame o mar e a sua plenitude; o mundo, e os que nele habitam.
Os rios batam as palmas; regozijem-se também as montanhas,
Perante a face do Senhor, porque vem a julgar a terra; com justiça julgará o
mundo, e o povo com eqüidade."
Salmos 98:7-9
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository