Fala, galera! Tudo bem?
Hoje iremos abordar o pattern Bridge. Cobriremos seus conceitos, usos e exemplos. Vamos lá!
Bridge
Este padrão é utilizado quando queremos separar a abstração da implementação, sendo que, justamente por trabalhar com a forma que interfaces e classes são elaboradas, é considerado um padrão estrutural.
Sua aplicação pode ser encontrada em pontes de conexão com bancos de dados JDBC, implementações de renderização gráfica em diferentes plataformas, e até mesmo a implementação de listas do Java.
Este padrão tem as seguintes características que gostaria de destacar:
- Capacidade de evitar uma ligação permanente entre uma abstração e implementação
- Proteção dos clientes com relação a alterações de implementação
- Capacidade de esconder completamente uma implementação do cliente
- Menor complexidade pra gerenciar hierarquias
A primeira, capacidade de evitar uma ligação permanente entre uma abstração e implementação pode ser útil quando desejamos alterar a implementação de uma janela em tempo de execução, como é usado no Windows, por exemplo.
Outro ponto bem interessante, é que o Bridge garante uma proteção aos clientes, contra alteração de implementações, já que permite retrocompatibilidade.
Em linguagens como C++ e C#, a capacidade de esconder a implementação completamente permite que a classe esteja inadvertidamente à disposição do cliente.
Permitir a gestão facilitada de um conjunto grande de hierarquias e extensões de uma abstração facilita quando uma abstração que tem muitas extensões e hierarquia, já que o padrão permite uma hierarquia de chamadas generalizada, separando as possíveis implementações.
Abaixo, temos o diagrama de classes que exemplifica a estrutura do padrão Bridge:
Acima, temos:
- Abstraction: classe abstrata que contém os membros que definem a abstração de negócios e suas funcionalidades. Contém uma referencia para o objeto do tipo Bridge e também é responsável por ser a classe base para outras abstrações.
- Redefined Abstraction: essa classe herda da classe Abstraction, estendendo a interface definida por ela.
- Bridge: interface que age como uma ponte entre a classe de abstração e de implementação, e também tem a funcionalidade de tornar a classe de implementação independente da classe de abstração.
- Implementation A e B: estas são classes que implementam a interface Bridge e também responsáveis por prover os detalhes de implementação para a abstração associada a ponte.
O código C# para o exemplo acima, seria:
public abstract class Abstracao
{
public Bridge Ponte { get; set; }
public virtual void Operacao()
{
Console.WriteLine("Ponte:Operacao()";
Ponte.OperacaoImplementada();
}
}
public class AbstracaoExtendida : Abstracao
{
public override void Operacao()
{
Console.WriteLine("AbstracaoExtendida:Operacao()";
Ponte.OperacaoImplementada();
}
}
public interface Bridge
{
void OperacaoImplementada();
}
public class ImplementacaoA : Bridge
{
public void OperacaoImplementada()
{
Console.WriteLine("ImplementacaoA:OperacaoImplementada()");
}
}
public class ImplementacaoB : Bridge
{
public void OperacaoImplementada()
{
Console.WriteLine("ImplementacaoB:OperacaoImplementada()");
}
}
Podemos notar que temos uma base, que se mantém fiel a sua estrutura o tempo todo para os clientes, mas que dependendo da ponte associada a propriedade Ponte, podemos ter funcionalidade diferentes, sem comprometer o cliente.
Vamos ao resumo do pattern para finalizar com um exemplo mais concreto.
Ficha Resumo
- Nome: Bridge
- Objetivo/intenção: utilizado quando queremos separar a abstração da implementação, através de uma interface que serve como ponte para as implementações. Muito útil também quando temos uma grande quantidade de hierarquias e extensões para gerenciar
- Motivação: proteger os clientes contra quebras de contrato no uso da abstração e facilitar o gerenciamento de grandes quantidades de hierarquias e extensões em sistemas mais complexos
- Aplicabilidade: o padrão Bridge é bastante utilizado em estruturas como pontes para conexões a bancos de dados JDBC, implementações de renderização gráfica em diferentes plataformas, e uma das mais famosas, a implementação de listas no Java. Com ele os clientes acessam diretamente a abstração sem a necessidade de conhecer qual a implementação, tornando esta totalmente oculta para os clientes e estes protegidos de modificações realizadas nas implementações
- Estrutura: abaixo temos a estrutura UML do pattern, descrita no livro do GoF. Message é uma classe abstrata que contém as propriedades e métodos abstratos para envio de mensagens. Temos duas extensões desta classe: SystemMessage e UserMessage, sendo que UserMessage contém de extra a propriedade UserComments, agregando uma funcionalidade extra. Temos uma interface IMessageSender, interface referenciada na propriedade MessageSender da classe abstrata Message, e as implementações EmailSender, WebServiceSender e MSMQSender, que são diferentes implementações de IMessageSender
A classe Message, assim como suas extensões, utilizarão a interface IMessageSender para envio da mensagem, podendo estes serem informados através de configurações, injeção de dependência, etc.
- Consequências: o padrão Bridge facilita quando temos uma quantidade grande de hierarquias e extensões, facilitando o uso das implementações, isolando elas do cliente, assim como uma melhor separação entre abstração e implementação. Em contrapartida, temos um aumento considerável na quantidade e complexidade do código, dada a criação de várias classes adicionais
- Implementações: abaixo, temos um exemplo de código para a estrutura de classes descrita no diagrama em “Estrutura”:
public abstract class Message
{
public IMessageSender MessageSender { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public abstract void Send();
}
public class SystemMessage : Message
{
public override void Send()
{
MessageSender.SendMessage(Subject, Body);
}
}
public class UserMessage : Message
{
public string UserComments { get; set; }
public override void Send()
{
string newBody = string.Format("{0}\nUserComments: {1}", Body, UserComments);
MessageSender.SendMessage(Subject, newBody);
}
}
public interface IMessageSender
{
void SendMessage(string subject, string body);
}
public class MSMQSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("MSMQ\n{0}\n{1}\n", subject, body);
}
}
public class WebServiceSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("Web Service\n{0}\n{1}\n", subject, body);
}
}
public class EmailSender : IMessageSender
{
public void SendMessage(string subject, string body)
{
Console.WriteLine("Email\n{0}\n{1}\n", subject, body);
}
}
class Program
{
static void Main(string[] args)
{
IMessageSender email = new EmailSender();
IMessageSender web = new WebServiceSender();
IMessageSender queue = new MSMQSender();
Message message = new SystemMessage();
message.Subject = "Test";
message.Body = "Test Message";
message.MessageSender = email;
message.Send();
message.MessageSender = queue;
message.Send();
message.MessageSender = web;
message.Send();
UserMessage userMsg = new UserMessage();
userMsg.Subject = "User Test';
userMsg.Body = "User Test Message";
userMsg.UserComments = "Some comments about the message";
userMsg.MessageSender = email;
userMsg.Send();
Console.ReadKey();
}
}
- Usos conhecidos: Muito utilizado na definição de sistemas de renderização gráfica multi-plataforma, pontes de conexões de bancos de dados JDBC e o mais famoso, a implementação de listas do Java
- Padrões relacionados: Adapter, Strategy e Template Method
Concluindo
Este foi o padrão Bridge, geralmente utilizado quando temos hierarquias muito grandes e queremos separar a abstração da implementação, sem comprometer os clientes, protegendo eles de mudanças nas implementações.
Na próxima parte desta série vamos abordar o padrão Decorator.
Aguardo feedbacks e dúvidas de vocês, seja aqui no iMasters ou através das redes sociais.
Um abraço!