Menu
Coddy logo textTech

Interface-Design in C++

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 61 von 104.

Ein Interface in C++ ist eine abstrakte Klasse, in der alle Elementfunktionen rein virtuell sind. Im Gegensatz zu abstrakten Klassen, die eine gewisse Implementierung enthalten können, definieren Interfaces nur, welche Operationen existieren müssen – sie legen einen Vertrag ohne jegliches Verhalten fest.

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

Dieses Drawable-Interface garantiert, dass jede implementierende Klasse über draw()- und resize()-Methoden verfügen wird, sagt aber nichts darüber aus, wie diese funktionieren. Diese Trennung ist leistungsstark – Code kann vom Interface abhängen, ohne etwas über die konkreten Typen zu wissen.

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 { /* Button-Größe ändern */ }
};

Eine Klasse kann mehrere Schnittstellen implementieren, was flexible Designs ermöglicht, bei denen Objekte verschiedene Rollen erfüllen können:

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

Interfaces fördern eine lose Kopplung – Ihr Code hängt von Abstraktionen ab statt von konkreten Implementierungen, was die Erweiterung und das Testen erleichtert.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Geräteverwaltungssystem erstellen, das zeigt, wie Interfaces Verträge für verschiedene Arten von Geräten definieren. Sie werden zwei separate Interfaces erstellen, die Geräte implementieren können, und dann konkrete Geräteklassen bauen, die einen oder beide dieser Verträge erfüllen.

Sie werden Ihren Code über drei Dateien organisieren:

  • Interfaces.h: Definieren Sie zwei rein abstrakte Klassen (Interfaces), die unterschiedliche Gerätefunktionen repräsentieren:

    Powerable — jedes Gerät, das ein- und ausgeschaltet werden kann:

    • Eine rein virtuelle powerOn() Methode
    • Eine rein virtuelle powerOff() Methode
    • Eine rein virtuelle getPowerStatus() Methode, die einen std::string zurückgibt
    • Ein virtueller Destruktor

    Connectable — jedes Gerät, das sich mit einem Netzwerk verbinden kann:

    • Eine rein virtuelle connect(const std::string& network) Methode
    • Eine rein virtuelle disconnect() Methode
    • Eine rein virtuelle getConnectionInfo() Methode, die einen std::string zurückgibt
    • Ein virtueller Destruktor
  • Devices.h: Implementieren Sie konkrete Geräteklassen, die diese Interfaces nutzen:

    Lamp — implementiert nur Powerable:

    • Ein privates bool isOn Mitglied (startet als false)
    • Ein privates std::string name Mitglied
    • Ein Konstruktor, der den Namen der Lampe entgegennimmt
    • powerOn() setzt isOn auf true und gibt aus: <name>: Light turned on
    • powerOff() setzt isOn auf false und gibt aus: <name>: Light turned off
    • getPowerStatus() gibt "ON" oder "OFF" basierend auf dem Zustand zurück

    SmartTV — implementiert sowohl Powerable als auch Connectable:

    • Private Mitglieder: bool isOn (startet false), std::string brand, std::string currentNetwork (startet leer)
    • Ein Konstruktor, der die Marke des Fernsehers entgegennimmt
    • powerOn() setzt isOn auf true und gibt aus: <brand> TV: Powered on
    • powerOff() setzt isOn auf false, leert das Netzwerk und gibt aus: <brand> TV: Powered off
    • getPowerStatus() gibt "ON" oder "OFF" zurück
    • connect() speichert den Netzwerknamen und gibt aus: <brand> TV: Connected to <network>
    • disconnect() leert das Netzwerk und gibt aus: <brand> TV: Disconnected
    • getConnectionInfo() gibt "Connected to <network>" zurück, wenn verbunden, oder "Not connected", wenn leer
  • main.cpp: Lesen Sie drei Eingaben ein (jede in einer separaten Zeile):
    1. Lampenname
    2. TV-Marke
    3. Netzwerkname

    Erstellen Sie eine Lamp und einen SmartTV. Demonstrieren Sie, wie dasselbe Interface mit verschiedenen Geräten verwendet werden kann:

    Arbeiten Sie zuerst mit beiden Geräten über das Powerable Interface. Speichern Sie Pointer auf beide in einem Array von Powerable*, durchlaufen Sie dieses in einer Schleife und rufen Sie für jedes powerOn() auf, gefolgt von der Ausgabe ihres Status als: Status: <powerStatus>

    Geben Sie eine Leerzeile aus und arbeiten Sie dann mit dem SmartTV über das Connectable Interface. Erstellen Sie einen Connectable* Pointer auf Ihren SmartTV, rufen Sie connect() mit dem Netzwerknamen auf und geben Sie aus: Connection: <connectionInfo>

    Geben Sie eine weitere Leerzeile aus, schalten Sie dann beide Geräte über das Powerable Array aus und zeigen Sie ihren endgültigen Status an.

Zum Beispiel mit den Eingaben Desk Lamp, Samsung und 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

Beachten Sie, wie der SmartTV entweder als Powerable oder als Connectable behandelt werden kann, je nachdem, welchen Interface-Pointer Sie verwenden. Diese Flexibilität ist die Stärke der Implementierung mehrerer Interfaces — Ihr Code kann mit jedem Gerät arbeiten, das den benötigten Vertrag erfüllt, ohne den konkreten Typ kennen zu müssen.

Spickzettel

Ein Interface in C++ ist eine abstrakte Klasse, in der alle Elementfunktionen rein virtuell (pure virtual) sind. Interfaces definieren nur, welche Operationen existieren müssen, ohne jegliche Implementierung:

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

Klassen implementieren Interfaces, indem sie alle rein virtuellen Funktionen überschreiben:

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

Eine Klasse kann mehrere Interfaces mittels Mehrfachvererbung implementieren:

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

Interfaces fördern eine lose Kopplung, indem sie es ermöglichen, dass Code von Abstraktionen anstatt von konkreten Implementierungen abhängt.

Probier es selbst

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

using namespace std;

int main() {
    // Eingaben lesen
    string lampName;
    string tvBrand;
    string networkName;
    
    getline(cin, lampName);
    getline(cin, tvBrand);
    getline(cin, networkName);
    
    // TODO: Ein Lamp- und ein SmartTV-Objekt erstellen
    
    // TODO: Ein Array von Powerable*-Pointern erstellen, das beide Geräte enthält
    
    // TODO: Das Array durchlaufen und powerOn() für jedes Gerät aufrufen
    // Nach jedem powerOn(), ausgeben: Status: <powerStatus>
    
    // TODO: Eine Leerzeile ausgeben
    
    // TODO: Einen Connectable*-Pointer auf den SmartTV erstellen
    // connect() mit dem Netzwerknamen aufrufen
    // Ausgeben: Connection: <connectionInfo>
    
    // TODO: Eine Leerzeile ausgeben
    
    // TODO: Das Powerable-Array durchlaufen und powerOff() für jedes Gerät aufrufen
    // Nach jedem powerOff(), ausgeben: Status: <powerStatus>
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung