Diseño de interfaces en C++
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 61 de 104.
Una interfaz en C++ es una clase abstracta donde cada función miembro—excepto el destructor (virtual)—es puramente virtual. A diferencia de las clases abstractas que pueden contener alguna implementación, las interfaces definen solo qué operaciones deben existir: especifican un contrato sin ningún comportamiento.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Esta interfaz Drawable garantiza que cualquier clase que la implemente tendrá los métodos draw() y resize(), pero no dice nada sobre cómo funcionan. Esta separación es poderosa: el código puede depender de la interfaz sin saber nada sobre los 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ón */ }
};Una clase puede implementar múltiples interfaces, permitiendo diseños flexibles donde los objetos pueden cumplir diferentes roles:
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; }
};Las interfaces promueven el bajo acoplamiento: su código depende de abstracciones en lugar de implementaciones concretas, lo que facilita su extensión y prueba.
Desafío
FácilVamos a construir un sistema de gestión de dispositivos que demuestre cómo las interfaces definen contratos para diferentes tipos de dispositivos. Crearás dos interfaces separadas que los dispositivos pueden implementar, y luego construirás clases de dispositivos concretas que cumplan con uno o ambos contratos.
Organizarás tu código en tres archivos:
Interfaces.h: Define dos clases abstractas puras (interfaces) que representan diferentes capacidades de los dispositivos:Powerable— cualquier dispositivo que pueda encenderse y apagarse:- Un método virtual puro
powerOn() - Un método virtual puro
powerOff() - Un método virtual puro
getPowerStatus()que devuelve unstd::string - Un destructor virtual
Connectable— cualquier dispositivo que pueda conectarse a una red:- Un método virtual puro
connect(const std::string& network) - Un método virtual puro
disconnect() - Un método virtual puro
getConnectionInfo()que devuelve unstd::string - Un destructor virtual
- Un método virtual puro
Devices.h: Implementa clases de dispositivos concretas que utilicen estas interfaces:Lamp— implementa soloPowerable:- Un miembro privado
bool isOn(comienza comofalse) - Un miembro privado
std::string name - Un constructor que recibe el nombre de la lámpara
powerOn()estableceisOna true e imprime:<name>: Light turned onpowerOff()estableceisOna false e imprime:<name>: Light turned offgetPowerStatus()devuelve"ON"o"OFF"según el estado
SmartTV— implementa tantoPowerablecomoConnectable:- Miembros privados:
bool isOn(comienza en false),std::string brand,std::string currentNetwork(comienza vacío) - Un constructor que recibe la marca del televisor
powerOn()estableceisOna true e imprime:<brand> TV: Powered onpowerOff()estableceisOna false, limpia la red e imprime:<brand> TV: Powered offgetPowerStatus()devuelve"ON"o"OFF"connect()almacena el nombre de la red e imprime:<brand> TV: Connected to <network>disconnect()limpia la red e imprime:<brand> TV: DisconnectedgetConnectionInfo()devuelve"Connected to <network>"si está conectado, o"Not connected"si está vacío
- Un miembro privado
main.cpp: Lee tres entradas (cada una en una línea separada):- Nombre de la lámpara
- Marca del televisor
- Nombre de la red
Crea una
Lampy unaSmartTV. Demuestra cómo se puede usar la misma interfaz con diferentes dispositivos:Primero, trabaja con ambos dispositivos a través de la interfaz
Powerable. Almacena punteros a ambos en un arreglo dePowerable*, luego recorre el arreglo y llama apowerOn()en cada uno, seguido de la impresión de su estado como:Status: <powerStatus>Imprime una línea en blanco, luego trabaja con la SmartTV a través de la interfaz
Connectable. Crea un punteroConnectable*a tu SmartTV, llama aconnect()con el nombre de la red e imprime:Connection: <connectionInfo>Imprime otra línea en blanco, luego apaga ambos dispositivos a través del arreglo
Powerabley muestra su estado final.
Por ejemplo, con las entradas Desk Lamp, Samsung, y 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: OFFObserva cómo la SmartTV puede ser tratada como un Powerable o como un Connectable dependiendo de qué puntero de interfaz utilices. Esta flexibilidad es el poder de implementar múltiples interfaces: tu código puede trabajar con cualquier dispositivo que cumpla con el contrato que necesita, sin conocer el tipo concreto.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// Leer entradas
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Crear un objeto Lamp y un objeto SmartTV
// TODO: Crear un arreglo de punteros Powerable* que contenga ambos dispositivos
// TODO: Recorrer el arreglo y llamar a powerOn() en cada dispositivo
// Después de cada powerOn(), imprimir: Status: <powerStatus>
// TODO: Imprimir una línea en blanco
// TODO: Crear un puntero Connectable* hacia la SmartTV
// Llamar a connect() con el nombre de la red
// Imprimir: Connection: <connectionInfo>
// TODO: Imprimir una línea en blanco
// TODO: Recorrer el arreglo Powerable y llamar a powerOff() en cada dispositivo
// Después de cada powerOff(), imprimir: Status: <powerStatus>
return 0;
}Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico