Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 MediaPlayer com estes métodos virtuais puros:

    • play(const std::string& filename) — reproduz o arquivo especificado
    • stop() — interrompe a reprodução
    • setVolume(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 LegacyAudioLib representa uma biblioteca antiga que funciona de forma diferente do que seu reprodutor espera. Ela deve ter:

    • loadAudio(const std::string& path) — imprime Legacy: Loading [path]
    • startPlayback() — imprime Legacy: Playback started
    • stopPlayback() — imprime Legacy: Playback stopped
    • adjustVolume(double percentage) — recebe um decimal (0.0 a 1.0) e imprime Legacy: Volume set to [percentage] (mostre o valor decimal)
  • AudioAdapter.h: Construa o adaptador que faz a ponte entre as duas interfaces.

    Sua classe AudioAdapter deve herdar de MediaPlayer e manter uma referência a um objeto LegacyAudioLib. O adaptador traduz as chamadas da interface moderna para a legada:

    • play() deve chamar tanto loadAudio() quanto startPlayback() na biblioteca legada
    • stop() deve delegar para stopPlayback()
    • 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:

    1. Um nome de arquivo para reproduzir (string)
    2. Um nível de volume (inteiro, 0-100)

    Crie uma instância de LegacyAudioLib e um AudioAdapter que a envolva. Em seguida, use o adaptador através da interface MediaPlayer (armazene-o como um ponteiro MediaPlayer) para:

    1. Definir o volume para o nível de entrada
    2. Reproduzir o nome do arquivo de entrada
    3. 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 stopped

Com as entradas podcast.wav e 50:

Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stopped

Observe 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;
}
quiz iconTeste seus conhecimentos

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