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.
Aufgabe
EinfachErstellen 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 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: Implementiere konkrete Geräteklassen, die diese Schnittstellen verwenden:Lamp: implementiert nurPowerable:- Ein privates
bool isOn-Mitglied (startet alsfalse) - Ein privates
std::string name-Mitglied - 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 abhängig vom Zustand"ON"oder"OFF"zurück
SmartTV: implementiert sowohlPowerableals auchConnectable:- Private Mitglieder:
bool isOn(startet als false),std::string brand,std::string currentNetwork(startet leer) - Ein Konstruktor, der die Marke des Fernsehers entgegennimmt
powerOn()setztisOnauf true und gibt aus:<brand> TV: Powered onpowerOff()setztisOnauf false, leert 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()leert das Netzwerk und gibt aus:<brand> TV: DisconnectedgetConnectionInfo()gibt"Connected to <network>"zurück, wenn eine Verbindung besteht, oder"Not connected", wenn es leer ist
- Ein privates
main.cpp: Lies drei Eingaben ein (jeweils in einer separaten Zeile):- Name der Lampe
- Marke des Fernsehers
- Netzwerkname
Erstelle eine
Lampund einenSmartTV. 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 TypPowerable*, durchlaufe anschließend das Array und rufe bei jedempowerOn()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 einenConnectable*-Zeiger auf deinen SmartTV, rufeconnect()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: OFFBeachte, 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;
}
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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler