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 todas las funciones miembro son virtuales puras. 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, lo que permite 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 acoplamiento débil: 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, 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 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 false), std::string brand, std::string currentNetwork (comienza vacío)
    • Un constructor que recibe la marca de la TV
    • 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 de la TV
    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.

Hoja de referencia

Una interfaz en C++ es una clase abstracta donde todas las funciones miembro son virtuales puras. Las interfaces definen solo qué operaciones deben existir sin ninguna implementación:

class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize(double factor) = 0;
    virtual ~Drawable() = default;
};

Las clases implementan interfaces sobrescribiendo todas las funciones virtuales 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; }
};

Una clase puede implementar múltiples interfaces utilizando herencia múltiple:

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 acoplamiento débil al permitir que el código dependa de abstracciones en lugar de implementaciones concretas.

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