Desenvolvimento

31 dez, 2018

Arquitetura e desenvolvimento de software – Parte 09: Bridge

Publicidade

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!