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 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:
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
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:
NET - Unit of Work - Padrão Unidade de ...
Você já esta usando Padrões de Projeto (mesmo sem perceber)
Design Patterns - o padrão Factory
Design Patterns - Identificando e Aplicando padrões
Padrões de Projeto - Design Patterns
Implementando o padrão de projeto Facade
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