C# -  Padrões Estruturais Gof - Adapter


Neste artigo vou iniciar a apresentação dos padrões estruturais Gof, começando com o padrão estrutural Adapter.

Os padrões estruturais explicam como montar objetos e classes em estruturas maiores mas ainda mantêm essas estruturas flexíveis e eficientes. Eles se concentram em como as classes herdam umas das outras e como são compostas de outras classes.

A característica principal dos padrões estruturais é a composição de objetos.

Os 7 padrões estruturais Gof são :  Adapter,  Bridge, Composite,  Decorator, Facade, Flyeweight e Proxy.

Seguindo a ordem alfabética vamos iniciar com o padrão estrutural Adapter.



O padrão Adapter

O padrão Adapter atua como uma ponte entre duas interfaces incompatíveis. Este padrão envolve uma única classe chamada adaptador que é responsável pela comunicação entre duas interfaces independentes ou incompatíveis.

Assim, o padrão Adapter  adapta uma interface em outra de acordo com a expectativa do cliente e permite a colaboração de objetos com interfaces incompatíveis. Ele também é conhecido como Wrapper ou Invólucro.

As principais intenções do padrão Adapter são:

  1. Converter a interface de uma classe em outra;
  2. Envolver (Wrap) uma classe existente com uma nova interface;
  3. Introduzir um componente legado em um novo sistema;

Como exemplo de aplicação do Adapter temos um exemplo clássico descrito a seguir:

- Suponha que temos um fornecedor de onde são obtidos dados no formato XML.

E esses dados são consumidos por uma aplicação.

Então surgiu a necessidade de processar esses dados usando uma biblioteca para gerar gráficos. O detalhe é que essa biblioteca processa os dados no formato JSON.

Assim as interfaces são incompatíveis pois o formato disponibilizado é o XML e não o JSON.

Como resolver esse problema ?

Uma solução seria implementar um Adapter que converta os dados do formato XML para JSON e envie os dados no formato esperado pela biblioteca  que poderá consumir e processar os dados :



Assim, a aplicação do padrão Adapter resolve o problema.

Esse cenário é muito comum e o padrão adapter geralmente é usado nestes casos.

Diagrama UML

A seguir temos o diagrama UML do padrão Adapter segundo o Gof:

Os participantes são:

1- Target - Define a interface do domínio específico que o cliente usa.

Esta é uma interface que é usada pelo cliente para atingir sua funcionalidade/solicitação.

2- Adaptee (Adaptado) - Esta é uma classe que possui a funcionalidade exigida pelo cliente. No entanto, sua interface não é compatível com o cliente.

Ela é usada pela classe Adapter para reutilizar a funcionalidade existente e modificá-la para o uso desejado.

3- Adapter (Adaptador) -  É uma classe wrapper que implementa a Implementa a interface ITarget e modifica a solicitação específica disponível na classe Adaptee.

O Adapter implementa a interface Target e é composto com o Adpatee. Ela é responsável pela comunicação entre o Client e a Adaptee.

4- Client - Esta é uma classe que interage com um tipo que implementa a interface Target.

Esta classe irá interagir com a classe Adapter.  O Client visualiza somente a interface Target

Note que aqui temos a utilização de vários princípios da Orientação a objetos :

- Uso da composição de objetos
- A vinculação do cliente para uma interface e não para uma implementação
- A delegação usada para vincular um Adapter a um Adaptee
- A herança de interface usada para especificar a interface do Adapter

Tipos de Adaptadores (Adapters)

Existem dois tipos de Adapter :

1- Adaptador de Objeto ou Object Adapter

Essa implementação usa o princípio de composição do objeto: o adaptador implementa a interface de um objeto e encobre o outro. É a implementação mais popular e pode ser usada na grande maioria das linguagens.

Nesta implementação os clientes chamam operações em uma instância de Adapter. e Por sua vez, o Adapter chama operações de Adaptee que executam a solicitação.

2- Adaptador de Classe ou Class Adapter

Essa implementação usa a herança: o adaptador herda interfaces de ambos os objetos ao mesmo tempo. (Herança Múltipla).  Aqui é usada a herança múltipla e pode ser usada em linguagens como C++.

A principal diferença entre essas implementações é que  com o adaptador de classe, nós sub-classificamos o Target
e o Adaptee,  enquanto que na implementação do adaptador de objeto usamos composição para passar o request para o Adaptee.

Usos do padrão Adapter

Podemos usar o padrão Adapter quando :

- Um objeto precisa usar uma classe existente com uma interface incompatível;
- Você deseja criar uma classe reutilizável que coopere com classes que não possuem interfaces compatíveis;
- É necessário reutilizar uma classe que não tenha uma interface exigida pelo cliente;
- Você quer permitir que um sistema use classes de outro sistema que seja incompatível com ele;
- Você quer permitir a comunicação entre um sistema novo e um já existente que é independente um do outro;

Vantagens e desvantagens do padrão Adapter

As vantagens em usar o padrão são:

- O padrão Adapter aumenta a reutilização do código;
- Você pode reutilizar código entre plataformas diferentes;
- Permite a interação de dois ou mais objetos incompatíveis;


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 Adapter

  1. Novas interfaces e classes são introduzidas para envolver uma funcionalidade existente, o que contribui para a complexidade do código;
  2. O encaminhamento de solicitação adicional é introduzido entre o cliente e a classe Adaptee;

Aplicando o padrão Adapter

Veremos agora um exemplo prático de aplicação do padrão Adapter.

Neste exemplo temos um Sistema Escolar que disponibiliza uma relação de alunos no formato de um array de strings, e precisamos realizar o processamento das mensalidades usando um sistema que recebe a lista de alunos e faz o cálculo das mensalidades:

O problema é que este sistema espera receber uma lista de objetos alunos como um List<Aluno> e não no formato de um array de strings.  Assim, os dois sistemas são incompatíveis e o Sistema Escolar não pode chamar diretamente o SistemaMensalidades para processar as mensalidades pois ele espera receber uma lista de objetos e não um Array de strings

Aqui o padrão Adapter entra em ação. Vamos implementar o padrão Adapter e permitir que a interface dos dois sistemas possam interagir.

Na implementação o Sistema Escolar vai enviar a informação da lista de alunos no formato de um array de strings para o
Adapter e então o Adapter vai ler as informações dos alunos no formato de um array de strings e vai converter esta informação para uma lista de objetos aluno no formato usando a classe List, e a seguir vai enviar a lista para o método CalculaMensalidade do SistemaMensalidades e este método vai então calcular a mensalidade dos alunos.


Iremos fazer a implementação usando o Object Adapter onde usaremos o principio da composição objetos, e onde o adaptador implementa a interface de um objeto e encobre o outro.

Vamos então criar um projeto Console usando o .NET 5.0 e a linguagem C# e a seguir vamos criar a classe Aluno que representa o nosso modelo de domínio:

    public class Aluno
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Curso { get; set; }
        public decimal Mensalidade { get; set; }

        public Aluno(int id, string nome, string curso, decimal mensalidade)
        {
            Id = id;
            Nome = nome;
            Curso = curso;
            Mensalidade = mensalidade;
        }
    }

A seguir vamos criar a interface ITarget onde definimos o contrato ProcessaCalculoMensalidade() que processa a lista de alunos no formato de um array de string:

    public interface ITarget
    {
        void ProcessaCalculoMensalidade(string[,] alunosArray);
    }

Esta interface será implementada pelo Adapter e o Client irá usar este método para calcular as mensalidades.

Depois vamos criar a classe SistemaMensalidades que espera receber a lista de objetos Aluno, vai percorrer esta lista e vai calcular a mensalidade dos alunos:

    using Adapter2.Domain;
    using System;
    using System.Collections.Generic;
    public class SistemaMensalidades
    {
        public void CalculaMensalidade(List<Aluno> listaAlunos)
        {
            foreach (Aluno aluno in listaAlunos)
            {
                //Usa uma lógica para calcular a mensalidade
                Console.WriteLine($"Aluno {aluno.Nome} " +
                    $"- Valor da mensalidade R$ {aluno.Mensalidade}");
            }
        }
    }

Aqui esta a funcionalidade que o cliente precisa usar para calcular a mensalidade dos alunos.  O Adapter vai usar esta classe para reutilizar a funcionalidade existente e modificá-la para o uso desejado.

Agora vamos criar a classe AlunoAdapter onde temos a implementação do Adapter:

using System;
using System.Collections.Generic;
namespace Adapter2.Adapter
{
    public class AlunoAdapter : ITarget
    {
        private SistemaMensalidades sistemaMensalidades = new SistemaMensalidades();

        public void ProcessaCalculoMensalidade(string[,] alunosArray)
        {
            string Id = null;
            string Nome = null;
            string Curso = null;
            string Mensalidade = null;
            List<Aluno> listaAlunos = new List<Aluno>();
            for (int i = 0; i < alunosArray.GetLength(0); i++)
            {
                for (int j = 0; j < alunosArray.GetLength(1); j++)
                {
                    if (j == 0)
                    {
                        Id = alunosArray[i, j];
                    }
                    else if (j == 1)
                    {
                        Nome = alunosArray[i, j];
                    }
                    else if (j == 1)
                    {
                        Curso = alunosArray[i, j];
                    }
                    else
                    {
                        Mensalidade = alunosArray[i, j];
                    }
                }
                //cria a lista de objetos aluno
                listaAlunos.Add(new Aluno(Convert.ToInt32(Id), Nome,
                                Curso, Convert.ToDecimal(Mensalidade)));
            }
            Console.WriteLine("O Adapter converteu o Array[] para List<> de Alunos\n");
            Console.WriteLine("E delegou ao SistemaMensalidades o processamento " +
                "da mensalidade dos alunos\n");
            sistemaMensalidades.CalculaMensalidade(listaAlunos);
        }
    }
}

Esta classe implementa a interface ITarget e fornece a implementação para o método ProcessaCalculoMensalidade e tem uma referência ao objeto SistemaMensalidades.

O método ProcessaCalculoMensalidade recebe as informações do aluno como uma matriz de string e, em seguida, converte a matriz de string em uma lista de alunos e chama o método CalculaMensalidade no objeto SistemaMensalidades  passando a lista de funcionários como um argumento.

Na classe Program, que faz o papel de Client, temos a relação de alunos como um array de strings e criamos uma instância de Adapter chamando o método ProcessaCalculoMensalidade() passando a lista como um argumento:

using Adapter2.Adapter;
using Adapter2.Target;
using System;
namespace Adapter2
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            string[,] alunosArray = new string[5, 4]
            {
                {"101","Maria","Artes","1000"},
                {"102","Pedro","Engenharia","2000"},
                {"103","Bianca","Veterinária","3000"},
                {"104","Pamela","Moda","900"},
                {"105","Sergio","Desenho","850"}
            };
            ITarget target = new AlunoAdapter();
            Console.WriteLine("SistemaMensalidades passa o " +
                              "array de string para o Adapter\n");
            target.ProcessaCalculoMensalidade(alunosArray);
            Console.ReadKey();
        }
    }
}

Aqui a instancia do Adapter vai fazer a conversão permitindo a comunicação entre os dois sistemas que antes eram incompatíveis.

Executando o projeto teremos o resultado:

Embora seja um exemplo básico fica bem claro a atuação e o papel do Adpater.

Pegue o código do projeto aqui : Adapter1.zip

(Disse Jesus) "E, se o teu olho te escandalizar, lança-o fora; melhor é para ti entrares no reino de Deus com um só olho do que, tendo dois olhos, seres lançado no fogo do inferno, Onde o seu bicho não morre, e o fogo nunca se apaga."
Marcos 9:47,48

Referências:


José Carlos Macoratti