Design de Interfaces em C++
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 61 de 104.
Uma interface em C++ é uma classe abstrata onde todas as funções membro são virtuais puras. Ao contrário de classes abstratas que podem conter alguma implementação, as interfaces definem apenas quais operações devem existir - elas especificam um contrato sem qualquer comportamento.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Esta interface Drawable garante que qualquer classe implementadora terá os métodos draw() e resize(), mas não diz nada sobre como eles funcionam. Esta separação é poderosa - o código pode depender da interface sem saber nada sobre os tipos concretos.
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};
class Button : public Drawable {
std::string label;
public:
Button(std::string l) : label(l) {}
void draw() override { std::cout << "Drawing button: " << label << std::endl; }
void resize(double factor) override { /* redimensionar botão */ }
};Uma classe pode implementar múltiplas interfaces, permitindo designs flexíveis onde objetos podem desempenhar diferentes papéis:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};Interfaces promovem baixo acoplamento - seu código depende de abstrações em vez de implementações concretas, tornando-o mais fácil de estender e testar.
Desafio
FácilVamos construir um sistema de gerenciamento de dispositivos que demonstra como as interfaces definem contratos para diferentes tipos de dispositivos. Você criará duas interfaces separadas que os dispositivos podem implementar e, em seguida, construirá classes de dispositivos concretas que cumprem um ou ambos os contratos.
Você organizará seu código em três arquivos:
Interfaces.h: Defina duas classes abstratas puras (interfaces) que representam diferentes capacidades de dispositivos:Powerable— qualquer dispositivo que possa ser ligado e desligado:- Um método virtual puro
powerOn() - Um método virtual puro
powerOff() - Um método virtual puro
getPowerStatus()retornando umastd::string - Um destrutor virtual
Connectable— qualquer dispositivo que possa se conectar a uma rede:- Um método virtual puro
connect(const std::string& network) - Um método virtual puro
disconnect() - Um método virtual puro
getConnectionInfo()retornando umastd::string - Um destrutor virtual
- Um método virtual puro
Devices.h: Implemente classes de dispositivos concretas que usam essas interfaces:Lamp— implementa apenasPowerable:- Um membro privado
bool isOn(começa comofalse) - Um membro privado
std::string name - Um construtor que recebe o nome da lâmpada
powerOn()defineisOncomo true e imprime:<name>: Light turned onpowerOff()defineisOncomo false e imprime:<name>: Light turned offgetPowerStatus()retorna"ON"ou"OFF"com base no estado
SmartTV— implementa tantoPowerablequantoConnectable:- Membros privados:
bool isOn(começa false),std::string brand,std::string currentNetwork(começa vazio) - Um construtor que recebe a marca da TV
powerOn()defineisOncomo true e imprime:<brand> TV: Powered onpowerOff()defineisOncomo false, limpa a rede e imprime:<brand> TV: Powered offgetPowerStatus()retorna"ON"ou"OFF"connect()armazena o nome da rede e imprime:<brand> TV: Connected to <network>disconnect()limpa a rede e imprime:<brand> TV: DisconnectedgetConnectionInfo()retorna"Connected to <network>"se estiver conectado, ou"Not connected"se estiver vazio
- Um membro privado
main.cpp: Leia três entradas (cada uma em uma linha separada):- Nome da lâmpada
- Marca da TV
- Nome da rede
Crie uma
Lampe umaSmartTV. Demonstre como a mesma interface pode ser usada com diferentes dispositivos:Primeiro, trabalhe com ambos os dispositivos através da interface
Powerable. Armazene ponteiros para ambos em um array dePowerable*, então percorra o array e chamepowerOn()em cada um, seguido pela impressão de seus status como:Status: <powerStatus>Imprima uma linha em branco e, em seguida, trabalhe com a SmartTV através da interface
Connectable. Crie um ponteiroConnectable*para sua SmartTV, chameconnect()com o nome da rede e imprima:Connection: <connectionInfo>Imprima outra linha em branco e, em seguida, desligue ambos os dispositivos através do array
Powerablee mostre seus status finais.
Por exemplo, com as entradas Desk Lamp, Samsung e HomeWiFi:
Desk Lamp: Light turned on
Status: ON
Samsung TV: Powered on
Status: ON
Samsung TV: Connected to HomeWiFi
Connection: Connected to HomeWiFi
Desk Lamp: Light turned off
Status: OFF
Samsung TV: Powered off
Status: OFFObserve como a SmartTV pode ser tratada como Powerable ou Connectable, dependendo de qual ponteiro de interface você usa. Essa flexibilidade é o poder de implementar múltiplas interfaces — seu código pode funcionar com qualquer dispositivo que cumpra o contrato de que ele precisa, sem conhecer o tipo concreto.
Folha de consulta
Uma interface em C++ é uma classe abstrata onde todas as funções membros são virtuais puras. Interfaces definem apenas quais operações devem existir sem qualquer implementação:
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};As classes implementam interfaces sobrescrevendo todas as funções virtuais puras:
class Circle : public Drawable {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override { std::cout << "Drawing circle" << std::endl; }
void resize(double factor) override { radius *= factor; }
};Uma classe pode implementar múltiplas interfaces usando herança múltipla:
class Clickable {
public:
virtual void onClick() = 0;
virtual ~Clickable() = default;
};
class IconButton : public Drawable, public Clickable {
public:
void draw() override { std::cout << "Drawing icon" << std::endl; }
void resize(double factor) override { }
void onClick() override { std::cout << "Clicked!" << std::endl; }
};Interfaces promovem o baixo acoplamento, permitindo que o código dependa de abstrações em vez de implementações concretas.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// Ler as entradas
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Criar um objeto Lamp e um objeto SmartTV
// TODO: Criar um array de ponteiros Powerable* contendo ambos os dispositivos
// TODO: Percorrer o array e chamar powerOn() em cada dispositivo
// Após cada powerOn(), imprimir: Status: <powerStatus>
// TODO: Imprimir uma linha em branco
// TODO: Criar um ponteiro Connectable* para a SmartTV
// Chamar connect() com o nome da rede
// Imprimir: Connection: <connectionInfo>
// TODO: Imprimir uma linha em branco
// TODO: Percorrer o array Powerable e chamar powerOff() em cada dispositivo
// Após cada powerOff(), imprimir: Status: <powerStatus>
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 Formas3Construtores 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