Инкапсуляция

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

- что всё это значит и для чего этого вообще нужуно?

Ответ на этот вопрос хорошо ответит картинка ниже.

Пожалуй это лучшая иллюстрация понятия инкапсуляции. Данный объект часы нужен для получения текущего времени, время на часах можно настроить несколькими способами.

  1. Открыть крышку часов и покрутить шестерёнки (это может привести к поломке).
  2. Использовать специальный винт (безопасно).

Если вдуматься мы пользуемся многими вещами не вникая в то, как именно эти вещи работают.

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

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

В программировании всё точно так-же. У объектов имеются поля, к некоторым из этих полей нужно закрыть доступ, чтобы напрямую нельзя было менять значение. Как это сделать? Давайте разбираться - это просто!


Модификаторы доступа

В C++ существуют три модификатора доступа:

private (закрытые члены)
class BankAccount { private: double balance; // Скрытое поле std::string accountNumber; // Закрытый метод bool isValidAmount(double amount) { return amount > 0; } };
Пояснение:
  • Члены класса, объявленные как private, доступны только внутри методов этого класса

  • Поля обычно делают private, чтобы контролировать доступ к ним

  • Вспомогательные методы также часто объявляют private

public (открытые члены)
class BankAccount { public: // Открытый конструктор BankAccount(std::string accNumber, double initialBalance) : accountNumber(accNumber), balance(initialBalance) {} // Открытые методы void deposit(double amount) { if (isValidAmount(amount)) { balance += amount; } } double getBalance() const { return balance; } };
Пояснение:
  • Public-члены образуют интерфейс класса

  • Конструкторы обычно делают public, чтобы можно было создавать объекты

  • Методы для работы с объектом также объявляют public

protected (защищенные члены)
class Shape { protected: int x, y; // Доступны в наследниках public: void move(int newX, int newY) { x = newX; y = newY; } };
  • Protected-члены доступны внутри класса и в классах-наследниках

  • Используются при наследовании (хотя в этой лекции мы его не рассматриваем)

Про классы наследники будет рассказаоно далее в теме "Наследование"


Методы доступа (геттеры и сеттеры)

Геттеры (методы получения данных)
class Person { private: std::string name; int age; public: // Геттер для name std::string getName() const { return name; } // Геттер для age int getAge() const { return age; } };
Пояснение:
  • Позволяют безопасно получать значения private-полей

  • Обычно объявляются как const-методы

  • Могут форматировать или преобразовывать данные перед возвратом

Сеттеры (методы установки данных)
class Person { public: // Сеттер для name с проверкой void setName(const std::string& newName) { if (!newName.empty()) { name = newName; } } // Сеттер для age с проверкой void setAge(int newAge) { if (newAge >= 0 && newAge <= 120) { age = newAge; } } };
Пояснение:
  • Позволяют безопасно изменять private-поля

  • Могут включать проверку корректности новых значений

  • Могут выполнять дополнительные действия при изменении поля


Пример

class Temperature { private: double celsius; // Основное хранилище температуры public: // Конструктор с проверкой Temperature(double tempCelsius) { setCelsius(tempCelsius); } // Сеттер для Celsius void setCelsius(double temp) { if (temp >= -273.15) { // Абсолютный ноль celsius = temp; } } // Геттер для Celsius double getCelsius() const { return celsius; } // Геттер для Fahrenheit double getFahrenheit() const { return celsius * 9/5 + 32; } // Сеттер для Fahrenheit void setFahrenheit(double tempF) { setCelsius((tempF - 32) * 5/9); } }; int main() { Temperature t(25.0); // Корректное использование t.setFahrenheit(77.0); std::cout << "Celsius: " << t.getCelsius() << std::endl; std::cout << "Fahrenheit: " << t.getFahrenheit() << std::endl; // Некорректное значение будет отклонено t.setCelsius(-300.0); // Не изменит температуру std::cout << "After invalid set: " << t.getCelsius() << std::endl; return 0; }
Разбор примера:
  1. Основное хранилище (celsius) объявлено как private

  2. Все изменения происходят через сеттеры с проверкой

  3. Пользователь может работать с температурой в разных шкалах

  4. Некорректные значения автоматически отклоняются

  5. Внутренняя реализация скрыта (можно изменить хранение на Fahrenheit без изменения интерфейса)


Инкапсуляция и классы vs структуры

В C++ разница между class и struct только в умолчательном уровне доступа:

class MyClass { // По умолчанию private int hidden; // private public: int visible; // public }; struct MyStruct { // По умолчанию public int visible; // public private: int hidden; // private };
Рекомендации:
  • Используйте class для сложных объектов с поведением

  • Используйте struct для простых агрегатов данных (POD - Plain Old Data)

  • В любом случае явно указывайте модификаторы доступа для ясности


Дружественные функции и классы

Дружественные (friend) элементы - исключение из инкапсуляции:

class SecureBox { private: std::string secret; // Дружественная функция friend void displaySecret(const SecureBox& box); // Дружественный класс friend class SecurityService; }; void displaySecret(const SecureBox& box) { // Имеет доступ к private-полям std::cout << box.secret << std::endl; } class SecurityService { public: void inspect(const SecureBox& box) { std::cout << "Inspection: " << box.secret << std::endl; } };
Пояснение:
  • Friend-элементы имеют доступ к private-членам

  • Нарушают инкапсуляцию, поэтому должны использоваться обдуманно

  • Применяются для перегрузки операторов или в особых случаях


Практические рекомендации

  1. Делайте поля private по умолчанию

  2. Предоставляйте минимально необходимый public-интерфейс

  3. Используйте const для методов, не изменяющих состояние

  4. Избегайте избыточных сеттеров - не все поля должны быть изменяемы

  5. Инкапсулируйте сложную логику внутри методов

  6. Избегайте friend без крайней необходимости


Инкапсуляция - фундаментальный принцип ООП, который позволяет:

  • Защищать внутреннее состояние объектов

  • Скрывать сложность реализации

  • Обеспечивать стабильный интерфейс

  • Упрощать модификацию и сопровождение кода

Правильное применение инкапсуляции делает код:

  • Более надежным

  • Легче для понимания

  • Проще в тестировании

  • Гибче к изменениям


Комментарии

Добавить комментарий

Чтобы оставить комменатрий необходимо Авторизоваться