Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 un std::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 un std::string
    • Un destructor virtual
  • Devices.h: Implementa clases de dispositivos concretas que utilicen estas interfaces:

    Lamp — implementa solo Powerable:

    • Un miembro privado bool isOn (comienza como false)
    • Un miembro privado std::string name
    • Un constructor que recibe el nombre de la lámpara
    • powerOn() establece isOn a true e imprime: <name>: Light turned on
    • powerOff() establece isOn a false e imprime: <name>: Light turned off
    • getPowerStatus() devuelve "ON" o "OFF" según el estado

    SmartTV — implementa tanto Powerable como Connectable:

    • 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() establece isOn a true e imprime: <brand> TV: Powered on
    • powerOff() establece isOn a false, limpia la red e imprime: <brand> TV: Powered off
    • getPowerStatus() 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: Disconnected
    • getConnectionInfo() devuelve "Connected to <network>" si está conectado, o "Not connected" si está vacío
  • main.cpp: Lee tres entradas (cada una en una línea separada):
    1. Nombre de la lámpara
    2. Marca del televisor
    3. Nombre de la red

    Crea una Lamp y una SmartTV. 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 de Powerable*, luego recorre el arreglo y llama a powerOn() 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 puntero Connectable* a tu SmartTV, llama a connect() 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 Powerable y 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: OFF

Observa 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;
}
quiz iconPonte a prueba

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