Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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ść typu std::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ść typu std::string
    • Wirtualny destruktor
  • Devices.h: Zaimplementuj konkretne klasy urządzeń korzystające z tych interfejsów:

    Lamp: implementuje tylko Powerable:

    • Prywatne pole bool isOn (początkowo ma wartość false)
    • Prywatne pole std::string name
    • Konstruktor przyjmujący nazwę lampy
    • powerOn() ustawia isOn na true i wyświetla: <name>: Light turned on
    • powerOff() ustawia isOn na false i wyświetla: <name>: Light turned off
    • getPowerStatus() zwraca "ON" lub "OFF" zależnie od stanu

    SmartTV: implementuje zarówno Powerable, jak i Connectable:

    • Prywatne pola: bool isOn (początkowo ma wartość false), std::string brand, std::string currentNetwork (początkowo puste)
    • Konstruktor przyjmujący markę telewizora
    • powerOn() ustawia isOn na true i wyświetla: <brand> TV: Powered on
    • powerOff() ustawia isOn na false, czyści nazwę sieci i wyświetla: <brand> TV: Powered off
    • getPowerStatus() 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: Disconnected
    • getConnectionInfo() zwraca "Connected to <network>", jeśli urządzenie jest połączone, lub "Not connected", jeśli nie ma połączenia
  • main.cpp: Odczytaj trzy dane wejściowe (każdą w osobnym wierszu):
    1. Nazwa lampy
    2. Marka telewizora
    3. Nazwa sieci

    Utwórz obiekt Lamp i obiekt SmartTV. 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 tablicy Powerable*, a następnie przejdź po niej w pętli i wywołaj dla każdego powerOn(), 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źnik Connectable* do obiektu SmartTV, wywołaj connect() z nazwą sieci i wyświetl: Connection: <connectionInfo>

    Wyświetl kolejny pusty wiersz, a następnie wyłącz oba urządzenia za pomocą tablicy Powerable i 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: OFF

Zauważ, ż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;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online