Padrão Adapter
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 97 de 104.
O padrão Adapter permite que interfaces incompatíveis trabalhem juntas. Ele age como uma ponte entre duas classes, convertendo a interface de uma classe em uma interface que os clientes esperam. Isso é particularmente útil ao integrar código legado ou bibliotecas de terceiros que não correspondem à interface do seu sistema.
Imagine que você tem uma classe existente que faz o que você precisa, mas a interface dela não corresponde ao que o seu código espera. Em vez de modificar qualquer um dos lados, você cria um adaptador que envolve a classe existente e traduz as chamadas:
#include <iostream>
#include <string>
// Classe existente com interface incompatível
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Interface alvo que o cliente espera
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// O Adapter envolve o LegacyPrinter e implementa a interface Printer
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Traduz a chamada
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// O cliente usa a interface esperada
adapter.print("Hello World");
}O adaptador mantém uma referência para o LegacyPrinter e implementa a interface Printer. Quando print() é chamado, ele delega para printMessage(). O código do cliente trabalha com a interface Printer sem saber sobre a implementação legada por baixo.
Use o Adapter quando você quiser usar uma classe existente, mas sua interface não corresponde ao que você precisa, ou quando você precisa criar uma classe reutilizável que coopere com classes que possuem interfaces incompatíveis.
Desafio
FácilVamos construir um sistema de Adaptador de Reprodutor de Mídia que faz a ponte entre diferentes interfaces de bibliotecas de áudio. Imagine que você está integrando uma biblioteca de áudio legada em um aplicativo moderno de reprodução de mídia — a biblioteca antiga funciona perfeitamente, mas sua interface não corresponde ao que seu reprodutor espera. É exatamente aqui que o padrão Adapter brilha.
Você organizará seu código em quatro arquivos:
MediaPlayer.h: Defina a interface de destino que seu reprodutor de mídia moderno espera.Crie uma classe abstrata
MediaPlayercom estes métodos virtuais puros:play(const std::string& filename)— reproduz o arquivo especificadostop()— interrompe a reproduçãosetVolume(int level)— define o volume (0-100)
Inclua um destrutor virtual.
LegacyAudioLib.h: Crie a biblioteca de áudio "legada" existente com uma interface incompatível.Sua classe
LegacyAudioLibrepresenta uma biblioteca antiga que funciona de forma diferente do que seu reprodutor espera. Ela deve ter:loadAudio(const std::string& path)— imprimeLegacy: Loading [path]startPlayback()— imprimeLegacy: Playback startedstopPlayback()— imprimeLegacy: Playback stoppedadjustVolume(double percentage)— recebe um decimal (0.0 a 1.0) e imprimeLegacy: Volume set to [percentage](mostre o valor decimal)
AudioAdapter.h: Construa o adaptador que faz a ponte entre as duas interfaces.Sua classe
AudioAdapterdeve herdar deMediaPlayere manter uma referência a um objetoLegacyAudioLib. O adaptador traduz as chamadas da interface moderna para a legada:play()deve chamar tantoloadAudio()quantostartPlayback()na biblioteca legadastop()deve delegar parastopPlayback()setVolume()recebe um número inteiro (0-100), mas a biblioteca legada espera um decimal (0.0-1.0), então você precisará convertê-lo
main.cpp: Demonstre o adaptador em ação.Leia duas entradas:
- Um nome de arquivo para reproduzir (string)
- Um nível de volume (inteiro, 0-100)
Crie uma instância de
LegacyAudioLibe umAudioAdapterque a envolva. Em seguida, use o adaptador através da interfaceMediaPlayer(armazene-o como um ponteiroMediaPlayer) para:- Definir o volume para o nível de entrada
- Reproduzir o nome do arquivo de entrada
- Interromper a reprodução
Por exemplo, com as entradas song.mp3 e 75:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedCom as entradas podcast.wav e 50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedObserve como o código do cliente funciona inteiramente com a interface MediaPlayer — ele não tem ideia de que uma biblioteca legada está fazendo o trabalho real por baixo. O adaptador cuida de toda a tradução, incluindo a conversão do volume inteiro para decimal. Esta é a essência do padrão Adapter: fazer interfaces incompatíveis trabalharem juntas sem modificar nenhum dos lados.
Folha de consulta
O padrão Adapter permite que interfaces incompatíveis trabalhem juntas, agindo como uma ponte entre duas classes. Ele converte a interface de uma classe em uma interface que os clientes esperam.
Use o padrão Adapter quando você quiser usar uma classe existente, mas sua interface não corresponde ao que você precisa, ou ao integrar código legado ou bibliotecas de terceiros.
Estrutura
O padrão consiste em três componentes principais:
- Target Interface: A interface que o cliente espera
- Adaptee: A classe existente com uma interface incompatível
- Adapter: Envolve o adaptee e implementa a interface alvo, traduzindo as chamadas entre eles
Exemplo
#include <iostream>
#include <string>
// Classe existente com interface incompatível (Adaptee)
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Interface alvo que o cliente espera
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// O Adapter envolve o LegacyPrinter e implementa a interface Printer
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Traduz a chamada
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// O cliente usa a interface esperada
adapter.print("Hello World");
}O adapter mantém uma referência ao adaptee e implementa a interface alvo. Quando os métodos são chamados no adapter, ele delega para os métodos do adaptee, traduzindo as chamadas conforme necessário. O código do cliente trabalha com a interface alvo sem conhecer a implementação legada.
Experimente você mesmo
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// Ler as entradas
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: Criar uma instância de LegacyAudioLib
// TODO: Criar um AudioAdapter que envolva a biblioteca legada
// TODO: Armazenar o adaptador como um ponteiro MediaPlayer
// TODO: Usar a interface MediaPlayer para:
// 1. Definir o volume para o nível de entrada
// 2. Reproduzir o filename de entrada
// 3. Parar a reprodução
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como um Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico