Projektowanie interfejsów w C++
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 61 z 104.
Interfejs w C++ to klasa abstrakcyjna, w której każda funkcja składowa — z wyjątkiem (wirtualnego) destruktora — jest czysto wirtualna. W przeciwieństwie do klas abstrakcyjnych, które mogą zawierać pewną implementację, interfejsy określają tylko, jakie operacje muszą istnieć — definiują kontrakt bez żadnego zachowania.
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(double factor) = 0;
virtual ~Drawable() = default;
};Ten interfejs Drawable gwarantuje, że każda implementująca go klasa będzie miała metody draw() i resize(), ale nie określa, jak one działają. Taki rozdział jest bardzo przydatny — kod może zależeć od interfejsu, nie wiedząc nic o konkretnych typach.
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 { /* zmień rozmiar przycisku */ }
};Klasa może implementować wiele interfejsów, umożliwiając elastyczne projektowanie, w którym obiekty mogą pełnić różne role:
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; }
};Interfejsy sprzyjają luźnemu powiązaniu — Twój kod zależy od abstrakcji, a nie od konkretnych implementacji, co ułatwia jego rozszerzanie i testowanie.
Wyzwanie
ŁatwyZbudujmy system zarządzania urządzeniami, który pokazuje, jak interfejsy definiują kontrakty dla różnych typów urządzeń. Utworzysz dwa osobne interfejsy, które mogą implementować urządzenia, a następnie zbudujesz konkretne klasy urządzeń realizujące jeden lub oba z tych kontraktów.
Podzielisz kod na trzy pliki:
Interfaces.h: Zdefiniuj dwie czysto abstrakcyjne klasy (interfejsy) reprezentujące różne możliwości urządzeń:Powerable: dowolne urządzenie, które można włączać i wyłączać:- Czysto wirtualna metoda
powerOn() - Czysto wirtualna metoda
powerOff() - Czysto wirtualna metoda
getPowerStatus()zwracająca wartość typustd::string - Wirtualny destruktor
Connectable: dowolne urządzenie, które może łączyć się z siecią:- Czysto wirtualna metoda
connect(const std::string& network) - Czysto wirtualna metoda
disconnect() - Czysto wirtualna metoda
getConnectionInfo()zwracająca wartość typustd::string - Wirtualny destruktor
- Czysto wirtualna metoda
Devices.h: Zaimplementuj konkretne klasy urządzeń korzystające z tych interfejsów:Lamp: implementuje tylkoPowerable:- Prywatne pole
bool isOn(początkowo ma wartośćfalse) - Prywatne pole
std::string name - Konstruktor przyjmujący nazwę lampy
powerOn()ustawiaisOnna true i wyświetla:<name>: Light turned onpowerOff()ustawiaisOnna false i wyświetla:<name>: Light turned offgetPowerStatus()zwraca"ON"lub"OFF"zależnie od stanu
SmartTV: implementuje zarównoPowerable, jak iConnectable:- Prywatne pola:
bool isOn(początkowo ma wartość false),std::string brand,std::string currentNetwork(początkowo puste) - Konstruktor przyjmujący markę telewizora
powerOn()ustawiaisOnna true i wyświetla:<brand> TV: Powered onpowerOff()ustawiaisOnna false, czyści nazwę sieci i wyświetla:<brand> TV: Powered offgetPowerStatus()zwraca"ON"lub"OFF"connect()zapisuje nazwę sieci i wyświetla:<brand> TV: Connected to <network>disconnect()czyści nazwę sieci i wyświetla:<brand> TV: DisconnectedgetConnectionInfo()zwraca"Connected to <network>", jeśli urządzenie jest połączone, lub"Not connected", jeśli nie ma połączenia
- Prywatne pole
main.cpp: Odczytaj trzy dane wejściowe (każdą w osobnym wierszu):- Nazwa lampy
- Marka telewizora
- Nazwa sieci
Utwórz obiekt
Lampi obiektSmartTV. Pokaż, jak tego samego interfejsu można używać z różnymi urządzeniami:Najpierw obsłuż oba urządzenia za pomocą interfejsu
Powerable. Zapisz wskaźniki do obu urządzeń w tablicyPowerable*, a następnie przejdź po niej w pętli i wywołaj dla każdegopowerOn(), po czym wyświetl jego stan w formacie:Status: <powerStatus>Wyświetl pusty wiersz, a następnie obsłuż urządzenie SmartTV za pomocą interfejsu
Connectable. Utwórz wskaźnikConnectable*do obiektu SmartTV, wywołajconnect()z nazwą sieci i wyświetl:Connection: <connectionInfo>Wyświetl kolejny pusty wiersz, a następnie wyłącz oba urządzenia za pomocą tablicy
Powerablei wyświetl ich końcowy stan.
Na przykład, dla danych wejściowych Desk Lamp, Samsung i 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: OFFZauważ, że obiekt SmartTV może być traktowany jako Powerable lub Connectable, zależnie od tego, którego wskaźnika do interfejsu użyjesz. Ta elastyczność wynika z implementowania wielu interfejsów. Twój kod może współpracować z dowolnym urządzeniem spełniającym wymagany kontrakt, nie znając jego konkretnego typu.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Devices.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string lampName;
string tvBrand;
string networkName;
getline(cin, lampName);
getline(cin, tvBrand);
getline(cin, networkName);
// TODO: Utwórz obiekt Lamp i obiekt SmartTV
// TODO: Utwórz tablicę wskaźników Powerable* zawierającą oba urządzenia
// TODO: Przejdź przez tablicę i wywołaj powerOn() dla każdego urządzenia
// Po każdym wywołaniu powerOn() wypisz: Status: <powerStatus>
// TODO: Wypisz pustą linię
// TODO: Utwórz wskaźnik Connectable* do obiektu SmartTV
// Wywołaj connect() z nazwą sieci
// Wypisz: Connection: <connectionInfo>
// TODO: Wypisz pustą linię
// TODO: Przejdź przez tablicę Powerable i wywołaj powerOff() dla każdego urządzenia
// Po każdym wywołaniu powerOff() wypisz: Status: <powerStatus>
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online