Menu
Coddy logo textTech

Interface-Design in C++

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

Eine Schnittstelle in C++ ist eine abstracte Klasse, bei der jede Memberfunktion—mit Ausnahme des (virtuellen) Destruktors—rein virtuell ist. Im Gegensatz zu abstrakten Klassen, die eine gewisse Implementierung enthalten können, definieren Schnittstellen nur, welche Operationen vorhanden sein 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 die Methoden draw() und resize() verfügt, sagt jedoch nichts darüber aus, wie sie funktionieren. Diese Trennung ist leistungsstark – Code kann von dem 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 skalieren */ }
};

Eine Klasse kann mehrere Schnittstellen implementieren, wodurch flexible Entwürfe ermöglicht werden, in 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; }
};

Schnittstellen fördern eine lose Kopplung – dein Code hängt von Abstraktionen statt von konkreten Implementierungen ab, wodurch er sich leichter erweitern und testen lässt.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Geräteverwaltungssystem, das zeigt, wie Schnittstellen Verträge für verschiedene Gerätetypen definieren. Du wirst zwei separate Schnittstellen erstellen, die Geräte implementieren können, und anschließend konkrete Geräteklassen erstellen, die einen oder beide dieser Verträge erfüllen.

Du wirst deinen Code auf drei Dateien verteilen:

  • Interfaces.h: Definiere zwei rein abstrakte Klassen (Schnittstellen), die verschiedene Gerätefähigkeiten darstellen:

    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: Implementiere konkrete Geräteklassen, die diese Schnittstellen verwenden:

    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 abhängig vom Zustand "ON" oder "OFF" zurück

    SmartTV: implementiert sowohl Powerable als auch Connectable:

    • Private Mitglieder: bool isOn (startet als 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 eine Verbindung besteht, oder "Not connected", wenn es leer ist
  • main.cpp: Lies drei Eingaben ein (jeweils in einer separaten Zeile):
    1. Name der Lampe
    2. Marke des Fernsehers
    3. Netzwerkname

    Erstelle eine Lamp und einen SmartTV. Zeige, wie dieselbe Schnittstelle mit verschiedenen Geräten verwendet werden kann:

    Arbeite zuerst mit beiden Geräten über die Powerable-Schnittstelle. Speichere Zeiger auf beide in einem Array vom Typ Powerable*, durchlaufe anschließend das Array und rufe bei jedem powerOn() auf, gefolgt von der Ausgabe ihres Status im Format: Status: <powerStatus>

    Gib eine Leerzeile aus und arbeite anschließend über die Connectable-Schnittstelle mit dem SmartTV. Erstelle einen Connectable*-Zeiger auf deinen SmartTV, rufe connect() mit dem Netzwerknamen auf und gib Folgendes aus: Connection: <connectionInfo>

    Gib eine weitere Leerzeile aus, schalte anschließend beide Geräte über das Powerable-Array aus und zeige ihren endgültigen Status.

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

Beachte, wie SmartTV entweder als Powerable oder als Connectable behandelt werden kann, je nachdem, welchen Schnittstellenzeiger du verwendest. Diese Flexibilität ist der Vorteil der Implementierung mehrerer Schnittstellen. Dein Code kann mit jedem Gerät arbeiten, das den benötigten Vertrag erfüllt, ohne den konkreten Typ zu kennen.

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: Erstelle ein Lamp- und ein SmartTV-Objekt
    
    // TODO: Erstelle ein Array von Powerable*-Zeigern, das beide Geräte enthält
    
    // TODO: Durchlaufe das Array und rufe powerOn() für jedes Gerät auf
    // Nach jedem powerOn() ausgeben: Status: <powerStatus>
    
    // TODO: Gib eine leere Zeile aus
    
    // TODO: Erstelle einen Connectable*-Zeiger auf den SmartTV
    // Rufe connect() mit dem Netzwerknamen auf
    // Print: Connection: <connectionInfo>
    
    // TODO: Gib eine leere Zeile aus
    
    // TODO: Durchlaufe das Powerable-Array und rufe powerOff() für jedes Gerät auf
    // 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

Übe selbstständig: Online-C++-Compiler