Menu
Coddy logo textTech

Проектирование интерфейсов на C++

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 61 из 104.

Интерфейс в C++ — это абстрактный класс, в котором все функции-члены являются чисто виртуальными. В отличие от абстрактных классов, которые могут содержать некоторую реализацию, интерфейсы определяют только то, какие операции должны существовать — они задают контракт без какого-либо поведения.

class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize(double factor) = 0;
    virtual ~Drawable() = default;
};

Этот интерфейс Drawable гарантирует, что любой реализующий его класс будет иметь методы draw() и resize(), но ничего не говорит о том, как они работают. Такое разделение очень эффективно — код может зависеть от интерфейса, ничего не зная о конкретных типах.

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 { /* изменить размер кнопки */ }
};

Класс может реализовывать несколько интерфейсов, что позволяет создавать гибкие архитектуры, в которых объекты могут выполнять различные роли:

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; }
};

Интерфейсы способствуют слабой связанности — ваш код зависит от абстракций, а не от конкретных реализаций, что упрощает его расширение и тестирование.

challenge icon

Задание

Легко

Давайте создадим систему управления устройствами, которая демонстрирует, как интерфейсы определяют контракты для различных типов устройств. Вы создадите два отдельных интерфейса, которые могут реализовывать устройства, а затем создадите конкретные классы устройств, выполняющие один или оба этих контракта.

Вы организуете свой код в трех файлах:

  • Interfaces.h: Определите два чисто абстрактных класса (интерфейса), которые представляют различные возможности устройств:

    Powerable — любое устройство, которое можно включить и выключить:

    • Чисто виртуальный метод powerOn()
    • Чисто виртуальный метод powerOff()
    • Чисто виртуальный метод getPowerStatus(), возвращающий std::string
    • Виртуальный деструктор

    Connectable — любое устройство, которое может подключаться к сети:

    • Чисто виртуальный метод connect(const std::string& network)
    • Чисто виртуальный метод disconnect()
    • Чисто виртуальный метод getConnectionInfo(), возвращающий std::string
    • Виртуальный деструктор
  • Devices.h: Реализуйте конкретные классы устройств, использующие эти интерфейсы:

    Lamp — реализует только Powerable:

    • Приватный член bool isOn (начинается с false)
    • Приватный член std::string name
    • Конструктор, принимающий имя лампы
    • powerOn() устанавливает isOn в true и выводит: <name>: Light turned on
    • powerOff() устанавливает isOn в false и выводит: <name>: Light turned off
    • getPowerStatus() возвращает "ON" или "OFF" в зависимости от состояния

    SmartTV — реализует как Powerable, так и Connectable:

    • Приватные члены: bool isOn (начинается с false), std::string brand, std::string currentNetwork (изначально пустая строка)
    • Конструктор, принимающий бренд телевизора
    • powerOn() устанавливает isOn в true и выводит: <brand> TV: Powered on
    • powerOff() устанавливает isOn в false, очищает сеть и выводит: <brand> TV: Powered off
    • getPowerStatus() возвращает "ON" или "OFF"
    • connect() сохраняет имя сети и выводит: <brand> TV: Connected to <network>
    • disconnect() очищает сеть и выводит: <brand> TV: Disconnected
    • getConnectionInfo() возвращает "Connected to <network>", если подключено, или "Not connected", если строка пуста
  • main.cpp: Считайте три входных значения (каждое на отдельной строке):
    1. Имя лампы
    2. Бренд телевизора
    3. Имя сети

    Создайте объекты Lamp и SmartTV. Продемонстрируйте, как один и тот же интерфейс может использоваться с разными устройствами:

    Сначала поработайте с обоими устройствами через интерфейс Powerable. Сохраните указатели на оба устройства в массиве Powerable*, затем в цикле вызовите powerOn() для каждого, после чего выведите их статус в формате: Status: <powerStatus>

    Выведите пустую строку, затем поработайте со SmartTV через интерфейс Connectable. Создайте указатель Connectable* на ваш SmartTV, вызовите connect() с именем сети и выведите: Connection: <connectionInfo>

    Выведите еще одну пустую строку, затем выключите оба устройства через массив Powerable и покажите их итоговый статус.

Например, при входных данных Desk Lamp, Samsung и 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

Обратите внимание, как SmartTV может рассматриваться либо как Powerable, либо как Connectable, в зависимости от того, какой указатель на интерфейс вы используете. Эта гибкость — сила реализации нескольких интерфейсов: ваш код может работать с любым устройством, которое выполняет необходимый контракт, не зная его конкретного типа.

Шпаргалка

Интерфейс в C++ — это абстрактный класс, в котором все функции-члены являются чисто виртуальными. Интерфейсы определяют только то, какие операции должны существовать, без какой-либо реализации:

class Drawable {
public:
    virtual void draw() = 0;
    virtual void resize(double factor) = 0;
    virtual ~Drawable() = default;
};

Классы реализуют интерфейсы, переопределяя все чисто виртуальные функции:

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 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; }
};

Интерфейсы способствуют слабой связанности, позволяя коду зависеть от абстракций, а не от конкретных реализаций.

Попробуйте сами

#include <iostream>
#include <string>
#include "Devices.h"

using namespace std;

int main() {
    // Чтение входных данных
    string lampName;
    string tvBrand;
    string networkName;
    
    getline(cin, lampName);
    getline(cin, tvBrand);
    getline(cin, networkName);
    
    // TODO: Создайте объект Lamp и объект SmartTV
    
    // TODO: Создайте массив указателей Powerable*, содержащий оба устройства
    
    // TODO: Пройдите циклом по массиву и вызовите powerOn() для каждого устройства
    // После каждого powerOn() выведите: Status: <powerStatus>
    
    // TODO: Выведите пустую строку
    
    // TODO: Создайте указатель Connectable* на SmartTV
    // Вызовите connect() с именем сети
    // Выведите: Connection: <connectionInfo>
    
    // TODO: Выведите пустую строку
    
    // TODO: Пройдите циклом по массиву Powerable и вызовите powerOff() для каждого устройства
    // После каждого powerOff() выведите: Status: <powerStatus>
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование