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 en la que cada función miembro—excepto el destructor (virtual)—es virtual pura. A diferencia de las clases abstractas, que pueden contener parte de la implementación, las interfaces solo definen 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, lo que permite diseños flexibles en los que 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 un acoplamiento flexible: tu código depende de abstracciones en lugar de implementaciones concretas, lo que facilita su extensión y prueba.
Desafío
FácilConstruyamos un sistema de gestión de dispositivos que demuestre cómo las interfaces definen contratos para distintos tipos de dispositivos. Crearás dos interfaces independientes que los dispositivos pueden implementar y, después, construirás clases concretas de dispositivos que cumplan uno o ambos contratos.
Organizarás tu código en tres archivos:
Interfaces.h: Define dos clases abstractas puras (interfaces) que representan distintas 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 concretas de dispositivos que utilizan estas interfaces:Lamp: implementa únicamentePowerable:- Un miembro privado
bool isOn(comienza comofalse) - Un miembro privado
std::string name - Un constructor que recibe el nombre de la lámpara
powerOn()estableceisOnen true e imprime:<name>: Light turned onpowerOff()estableceisOnen false e imprime:<name>: Light turned offgetPowerStatus()devuelve"ON"u"OFF"según el estado
SmartTV: implementa tantoPowerablecomoConnectable:- Miembros privados:
bool isOn(comienza como false),std::string brand,std::string currentNetwork(comienza vacío) - Un constructor que recibe la marca del televisor
powerOn()estableceisOnen true e imprime:<brand> TV: Powered onpowerOff()estableceisOnen false, borra la red e imprime:<brand> TV: Powered offgetPowerStatus()devuelve"ON"u"OFF"connect()almacena el nombre de la red e imprime:<brand> TV: Connected to <network>disconnect()borra 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 independiente):- Nombre de la lámpara
- Marca del televisor
- Nombre de la red
Crea un
Lampy unSmartTV. Demuestra cómo se puede utilizar la misma interfaz con distintos dispositivos:Primero, trabaja con ambos dispositivos mediante la interfaz
Powerable. Almacena punteros a ambos en un arreglo dePowerable*; después, recorre el arreglo y llama apowerOn()en cada uno, seguido de la impresión de su estado con el formato:Status: <powerStatus>Imprime una línea en blanco; después, trabaja con el SmartTV mediante 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; después, apaga ambos dispositivos mediante el 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 SmartTV puede tratarse como Powerable o como Connectable, según el puntero de interfaz que utilices. Esta flexibilidad es la ventaja de implementar varias interfaces. Tu código puede trabajar con cualquier dispositivo que cumpla 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() {
// Lee las entradas
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Crea un objeto Lamp y un SmartTV
// TODO: Crea un array de punteros Powerable* que contenga ambos dispositivos
// TODO: Recorre el array y llama a powerOn() en cada dispositivo
// Después de cada powerOn(), imprime: Status: <powerStatus>
// TODO: Imprime una línea en blanco
// TODO: Crea un puntero Connectable* al SmartTV
// Llama a connect() con el nombre de la red
// Print: Connection: <connectionInfo>
// TODO: Imprime una línea en blanco
// TODO: Recorre el array Powerable y llama a powerOff() en cada dispositivo
// Después de cada powerOff(), imprime: 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éricoPractica por tu cuenta: Compilador de C++ online