Menu
Coddy logo textTech

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

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

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

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 — в зависимости от того, указатель на какой интерфейс вы используете. Именно эта гибкость является преимуществом реализации нескольких интерфейсов. Ваш код может работать с любым устройством, выполняющим нужный контракт, не зная его конкретный тип.

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

#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() с именем сети
    // Print: Connection: <connectionInfo>
    
    // TODO: Вывести пустую строку
    
    // TODO: Пройти по массиву Powerable и вызвать powerOff() для каждого устройства
    // После каждого powerOff() вывести: Status: <powerStatus>
    
    return 0;
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++