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 jede Elementfunktion—außer dem (virtuellen) Destruktor—rein virtuell ist. 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 die Methoden draw() und resize() verfügt, 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 einnehmen 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.
Aufgabe
EinfachLassen Sie uns ein Geräteverwaltungssystem erstellen, das zeigt, wie Schnittstellen Verträge für verschiedene Gerätetypen definieren. Sie werden zwei separate Schnittstellen 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 in drei Dateien organisieren:
Interfaces.h: Definieren Sie zwei rein abstrakte Klassen (Interfaces), die verschiedene Gerätefunktionen 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 einenstd::stringzurü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 einenstd::stringzurückgibt - Ein virtueller Destruktor
- Eine rein virtuelle
Devices.h: Implementieren Sie konkrete Geräteklassen, die diese Schnittstellen verwenden:Lamp— implementiert nurPowerable:- Ein privates
bool isOnMitglied (beginnt alsfalse) - Ein privates
std::string nameMitglied - Ein Konstruktor, der den Namen der Lampe entgegennimmt
powerOn()setztisOnauf true und gibt aus:<name>: Light turned onpowerOff()setztisOnauf false und gibt aus:<name>: Light turned offgetPowerStatus()gibt"ON"oder"OFF"basierend auf dem Zustand zurück
SmartTV— implementiert sowohlPowerableals auchConnectable:- Private Mitglieder:
bool isOn(beginnt als false),std::string brand,std::string currentNetwork(beginnt leer) - Ein Konstruktor, der die Marke des Fernsehers entgegennimmt
powerOn()setztisOnauf true und gibt aus:<brand> TV: Powered onpowerOff()setztisOnauf false, löscht das Netzwerk und gibt aus:<brand> TV: Powered offgetPowerStatus()gibt"ON"oder"OFF"zurückconnect()speichert den Netzwerknamen und gibt aus:<brand> TV: Connected to <network>disconnect()löscht das Netzwerk und gibt aus:<brand> TV: DisconnectedgetConnectionInfo()gibt"Connected to <network>"zurück, wenn verbunden, oder"Not connected", wenn leer
- Ein privates
main.cpp: Lesen Sie drei Eingaben ein (jede in einer separaten Zeile):- Lampenname
- TV-Marke
- Netzwerkname
Erstellen Sie eine
Lampund einenSmartTV. Demonstrieren Sie, wie dieselbe Schnittstelle mit verschiedenen Geräten verwendet werden kann:Arbeiten Sie zuerst mit beiden Geräten über die
Powerable-Schnittstelle. Speichern Sie Zeiger auf beide in einem Array vonPowerable*, durchlaufen Sie dieses dann in einer Schleife und rufen Sie für jedespowerOn()auf, gefolgt von der Ausgabe ihres Status als:Status: <powerStatus>Geben Sie eine Leerzeile aus und arbeiten Sie dann mit dem SmartTV über die
Connectable-Schnittstelle. Erstellen Sie einenConnectable*-Zeiger auf Ihren SmartTV, rufen Sieconnect()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: OFFBeachten Sie, wie der SmartTV entweder als Powerable oder als Connectable behandelt werden kann, je nachdem, welchen Interface-Zeiger Sie verwenden. Diese Flexibilität ist die Stärke der Implementierung mehrerer Schnittstellen — Ihr 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*-Pointern, das beide Geräte enthält
// TODO: Iteriere durch das Array und rufe powerOn() für jedes Gerät auf
// Nach jedem powerOn(), gib aus: Status: <powerStatus>
// TODO: Gib eine Leerzeile aus
// TODO: Erstelle einen Connectable*-Pointer auf den SmartTV
// Rufe connect() mit dem Netzwerknamen auf
// Gib aus: Connection: <connectionInfo>
// TODO: Gib eine Leerzeile aus
// TODO: Iteriere durch das Powerable-Array und rufe powerOff() für jedes Gerät auf
// Nach jedem powerOff(), gib aus: Status: <powerStatus>
return 0;
}Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container