Menu
Coddy logo textTech

Progettazione delle interfacce in C++

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 61 di 104.

Un’interfaccia in C++ è una classe astratta in cui ogni funzione membro—eccetto il distruttore (virtuale)—è puramente virtuale. A differenza delle classi astratte che possono contenere alcune implementazioni, le interfacce definiscono solo quali operazioni devono esistere: specificano un contratto senza alcun comportamento.

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

Questa interfaccia Drawable garantisce che qualsiasi classe che la implementi avrà i metodi draw() e resize(), ma non dice nulla su come funzionano. Questa separazione è potente: il codice può dipendere dall’interfaccia senza sapere nulla dei tipi concreti.

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 { /* ridimensiona il pulsante */ }
};

Una classe può implementare più interfacce, consentendo design flessibili in cui gli oggetti possono svolgere ruoli diversi:

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; }
};

Le interfacce favoriscono l’accoppiamento debole: il tuo codice dipende da astrazioni anziché da implementazioni concrete, rendendo più facile estenderlo e testarlo.

challenge icon

Sfida

Facile

Costruiamo un sistema di gestione dei dispositivi che mostra come le interfacce definiscono contratti per diversi tipi di dispositivi. Creerai due interfacce separate che i dispositivi possono implementare, poi creerai classi concrete per i dispositivi che soddisfano uno o entrambi questi contratti.

Organizzerai il codice in tre file:

  • Interfaces.h: definisci due classi astratte pure (interfacce) che rappresentano diverse funzionalità dei dispositivi:

    Powerable: qualsiasi dispositivo che può essere acceso e spento:

    • Un metodo virtuale puro powerOn()
    • Un metodo virtuale puro powerOff()
    • Un metodo virtuale puro getPowerStatus() che restituisce uno std::string
    • Un distruttore virtuale

    Connectable: qualsiasi dispositivo che può connettersi a una rete:

    • Un metodo virtuale puro connect(const std::string& network)
    • Un metodo virtuale puro disconnect()
    • Un metodo virtuale puro getConnectionInfo() che restituisce uno std::string
    • Un distruttore virtuale
  • Devices.h: implementa classi concrete per i dispositivi che usano queste interfacce:

    Lamp: implementa solo Powerable:

    • Un membro privato bool isOn (inizializzato a false)
    • Un membro privato std::string name
    • Un costruttore che accetta il nome della lampada
    • powerOn() imposta isOn su true e stampa: <name>: Light turned on
    • powerOff() imposta isOn su false e stampa: <name>: Light turned off
    • getPowerStatus() restituisce "ON" o "OFF" in base allo stato

    SmartTV: implementa sia Powerable che Connectable:

    • Membri privati: bool isOn (inizializzato a false), std::string brand, std::string currentNetwork (inizializzato come stringa vuota)
    • Un costruttore che accetta la marca del televisore
    • powerOn() imposta isOn su true e stampa: <brand> TV: Powered on
    • powerOff() imposta isOn su false, cancella la rete e stampa: <brand> TV: Powered off
    • getPowerStatus() restituisce "ON" o "OFF"
    • connect() memorizza il nome della rete e stampa: <brand> TV: Connected to <network>
    • disconnect() cancella la rete e stampa: <brand> TV: Disconnected
    • getConnectionInfo() restituisce "Connected to <network>" se è connesso, oppure "Not connected" se la rete è vuota
  • main.cpp: leggi tre input (ciascuno su una riga separata):
    1. Nome della lampada
    2. Marca del televisore
    3. Nome della rete

    Crea un Lamp e uno SmartTV. Mostra come si può usare la stessa interfaccia con dispositivi diversi:

    Per prima cosa, lavora con entrambi i dispositivi tramite l'interfaccia Powerable. Memorizza i puntatori a entrambi in un array di Powerable*, poi esegui un ciclo per chiamare powerOn() su ciascuno, seguito dalla stampa del loro stato nel formato: Status: <powerStatus>

    Stampa una riga vuota, poi lavora con lo SmartTV tramite l'interfaccia Connectable. Crea un puntatore Connectable* al tuo SmartTV, chiama connect() con il nome della rete e stampa: Connection: <connectionInfo>

    Stampa un'altra riga vuota, poi spegni entrambi i dispositivi tramite l'array Powerable e mostra il loro stato finale.

Per esempio, con gli input 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: OFF

Nota come SmartTV possa essere trattato come Powerable oppure come Connectable, a seconda del puntatore all'interfaccia che usi. Questa flessibilità è il vantaggio di implementare più interfacce. Il tuo codice può lavorare con qualsiasi dispositivo che soddisfi il contratto richiesto, senza conoscere il tipo concreto.

Provalo tu

#include <iostream>
#include <string>
#include "Devices.h"

using namespace std;

int main() {
    // Leggi gli input
    string lampName;
    string tvBrand;
    string networkName;
    
    getline(cin, lampName);
    getline(cin, tvBrand);
    getline(cin, networkName);
    
    // TODO: Crea un oggetto Lamp e un oggetto SmartTV
    
    // TODO: Crea un array di puntatori Powerable* contenente entrambi i dispositivi
    
    // TODO: Scorri l'array e chiama powerOn() su ciascun dispositivo
    // Dopo ogni chiamata a powerOn(), stampa: Status: <powerStatus>
    
    // TODO: Stampa una riga vuota
    
    // TODO: Crea un puntatore Connectable* a SmartTV
    // Chiama connect() con il nome della rete
    // Stampa: Connection: <connectionInfo>
    
    // TODO: Stampa una riga vuota
    
    // TODO: Scorri l'array Powerable e chiama powerOff() su ciascun dispositivo
    // Dopo ogni chiamata a powerOff(), stampa: Status: <powerStatus>
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online