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.
Sfida
FacileCostruiamo 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 unostd::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 unostd::string - Un distruttore virtuale
- Un metodo virtuale puro
Devices.h: implementa classi concrete per i dispositivi che usano queste interfacce:Lamp: implementa soloPowerable:- Un membro privato
bool isOn(inizializzato afalse) - Un membro privato
std::string name - Un costruttore che accetta il nome della lampada
powerOn()impostaisOnsu true e stampa:<name>: Light turned onpowerOff()impostaisOnsu false e stampa:<name>: Light turned offgetPowerStatus()restituisce"ON"o"OFF"in base allo stato
SmartTV: implementa siaPowerablecheConnectable:- 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()impostaisOnsu true e stampa:<brand> TV: Powered onpowerOff()impostaisOnsu false, cancella la rete e stampa:<brand> TV: Powered offgetPowerStatus()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: DisconnectedgetConnectionInfo()restituisce"Connected to <network>"se è connesso, oppure"Not connected"se la rete è vuota
- Un membro privato
main.cpp: leggi tre input (ciascuno su una riga separata):- Nome della lampada
- Marca del televisore
- Nome della rete
Crea un
Lampe unoSmartTV. 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 diPowerable*, poi esegui un ciclo per chiamarepowerOn()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 puntatoreConnectable*al tuo SmartTV, chiamaconnect()con il nome della rete e stampa:Connection: <connectionInfo>Stampa un'altra riga vuota, poi spegni entrambi i dispositivi tramite l'array
Powerablee 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: OFFNota 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;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online