Вы находитесь на странице: 1из 41

Александр Загоруйко © 2020

Encapsulation
У каждого объекта – свои поля
Каждый экземпляр класса (объект) при
создании получает в своё распоряжение
копию всех нестатических полей,
объявленных в классе. Это позволяет
отличать состояния разных объектов.
Однако, нестатических (впрочем, как и
статических) методов это не касается –
они не копируются на каждый объект!
На все объекты – один метод
Возникает вопрос, почему же тогда корректно
отрабатывает код вроде:
Person* a = new Person("Джон Cноу");
Person* b = new Person("Вася Пупкин");
a->Print(); // Джон Cноу
b->Print(); // Вася Пупкин
Как метод Print определяет, для какого именно
объекта он был вызван, и как он «видит»
состояние этого объекта без передачи
дополнительных параметров?
Указатель this
Дело в том, что когда метод класса
вызывается для обработки данных
конкретного объекта, этому методу
автоматически и неявно в качестве первого
параметра передается указатель на тот
объект, для которого метод был вызван. Этот
указатель имеет фиксированное имя this и
незаметно для программиста существует в
каждом нестатическом методе класса.
Как это работает

Когда мы пишем
a.Print();
для программы это что-то вроде:
Person::print(&a);
а сам метод выглядит примерно так:
void Print(Person* this) {
Свойства указателя this
1. Указатель this инициализируется
значением адреса объекта, для которого
вызван метод, перед началом
выполнения кода этого метода.
2. Идентификатор this является
служебным (ключевым) словом, то есть,
вручную создать указатель (переменную)
по имени this нельзя (даже в мейне).
Свойства указателя this

3. this является константным указателем,


т.е. изменять его нельзя, однако в каждом
нестатическом методе класса он
указывает именно на тот объект, для
которого этот метод вызывается.
4. Объект, который адресуется указателем
this, становится доступным внутри
нестатического метода именно с его
помощью.
Свойства указателя this
5. Внутри нестатических методов при
необходимости можно явно использовать
этот указатель, например так:
https://git.io/voqMk
Указатель this – это фундаментальное
понятие в ООП. И применять его в
будущем придётся ещё не раз… Как
показывает практика, вопросы про this –
любимые у Дмитрия 
Инкапсуляция
Инкапсуляция – это принцип, согласно
которому данные о свойствах объекта и
методы для работы с этими данными
объединены в единое целое - класс. При
этом главным требованием инкапсуляции
является санкционированный доступ к
полям класса, т.е. доступ не на прямую, а
через интерфейс класса (его открытые
методы-аксессоры).
Инкапсуляция
Инкапсуляция (encapsulation) — это
языковая конструкция (например, класс),
позволяющая связать данные (т.е. поля)
с методами, предназначенными для
обработки этих данных. Также к этому
понятию относится механизм языка,
позволяющий ограничить доступ одних
компонентов программы к другим.
Интерфейс и реализация
Инкапсуляция позволяет прикладному программисту не
задумываться о сложности реализации используемого
программного компонента, а взаимодействовать с ним
посредством предоставляемого интерфейса (публичных методов
и полей), а также объединить и защитить от некорректного
использования жизненно важные для компонента данные. При
этом программисту-пользователю предоставляется только
спецификация (интерфейс) объекта. Программист-пользователь
может взаимодействовать с объектом только через этот
интерфейс. Доступ к компонентам предоставляется с помощью
ключевого слова public. Программист-пользователь не может
напрямую обращаться к закрытым полям и методам – изменение
реализации (внутренних закрытых данных) возможно только
через методы интерфейса.
Пример из жизни
Идею инкапсуляции можно с лёгкостью
проиллюстрировать на примере любого бытового
прибора, скажем, телевизора. Есть сам телевизор
с пультом управления, и этого более чем
достаточно, чтобы успешно им пользоваться. При
этом не только нет необходимости, но и
категорически не рекомендуется самостоятельно
разбирать телевизор и копаться в его
внутренностях. Внутренности телевизора скрыты
разработчиками от конечных пользователей.
Пример
class Test {
// данные открытого интерфейса
public:
int a, b; // так очень редко делают
// метод открытого интерфейса
int returnFive() { return 5; }
// скрытые (инкапсулированные) данные
private:
int c, d;
// инкапсулированный метод
void doSomething() {}
};
Отличие классов и структур
Для структуры мы всегда имеем доступ к её
компонентам, для класса – только если они
объявлены как public. private компоненты доступны
только тому коду, который размещается внутри
этого же класса.
Спецификаторы доступа
Цели инкапсуляции
Основной целью инкапсуляции является
обеспечение согласованности
(целостности) внутреннего состояния
объекта. На практике, в языке C++ 95%
методов будут помечены как public, в то
время, как переменные, за редким
исключением, не должны быть публично
доступными.
Цели инкапсуляции

- предельная локализация изменений


при их необходимости
- прогнозируемость изменений (т.е.
какие изменения в коде нужно будет
сделать для заданного изменения
функциональности) и
прогнозируемость последствий этих
изменений
Цели инкапсуляции
- обеспечение безопасности
использования модуля, вынесение в
интерфейс только тех методов
обработки информации, которые не
могут испортить или удалить исходные
данные объектов или класса
- уменьшение сложности, при скрытии от
«внешнего мира» ненужных деталей
реализации
Спецификаторы доступа
Инкапсуляция реализуется за счёт использования
спецификаторов доступа:
private: компоненты класса доступны для
использования (чтения и записи) исключительно внутри
(в теле) текущего класса
protected: компоненты класса доступны внутри
текущего класса, а также внутри классов-наследников
(будем разбирать позже)
public: компоненты класса доступны вообще всем –
текущему классу, любым другим классам и глобальным
функциям.
Приватность полей
Итак, для обеспечения согласованности внутреннего
состояния объекта, необходимо помещать данные
класса в приватную секцию. Возникает вопрос, как
же потом получить доступ к закрытым полям?
class Person {
private:
int age;
};

void main() {
Person* p = new Person();
p->age = 25; // error: age has private access!
}
Методы-аксессоры

Для обеспечения санкционированного


доступа (как на чтение, так и на запись)
к приватным данным объектов класса
предназначены специальные методы-
аксессоры:
Инспекторы (геттеры)
Модификаторы (сеттеры)
Сеттер

Сеттер (устанавливающий метод,


модифицирующий метод, мутатор) – это
метод класса, предназначенный для
того, чтобы присвоить какое-либо
значение инкапсулированному полю
(иногда – нескольким полям). Обычно,
при этом сеттер обрабатывает
недопустимые варианты значений для
поля с помощью условных операторов.
Пример сеттера

public: void SetAge(int age) {


if (age >= 0 && age <= 100) {
this->age = age;
} else {
this->age = 18;
}
}
Геттер

Геттер (получатель) — это метод,


позволяющий получить данные, доступ
к которым напрямую ограничен (в силу
обеспечения механизма инкапсуляции).
В реализации геттеры проще сеттеров –
не принимают параметров, и не
выполняют никаких особых проверок.
Пример геттера
public: int GetAge() const {
return age;
}

Тип возвращаемого значения метода-


геттера обычно совпадает с типом
инкапсулированного поля. this перед
названием поля не пишется: нет параметра -
нет конфликта имён.
Сокрытие данных
 Придерживаясь принципа сокрытия данных, мы
объявили поле age в классе приватным, дав косвенный
доступ к нему через два метода с «говорящими»
именами.
 В данном случае, достигнут тот же эффект, как если бы
поле было просто public.
 На самом деле, это правильно с точки зрения
инкапсуляции. Поля age может вообще и не быть, а
пользователь будет по прежнему думать, что методы
связаны с каким-то полем. В будущем это позволит
заменить реализацию класса, не меняя его
интерфейс. Зачем – будет рассмотрено позже.
Необходимость геттеров
Зачем нужны геттеры, если они всё равно ничего
особенного не делают, а просто возвращают
значения полей? Вспомните, что ключевым
требованием инкапсуляции является
санкционированный доступ к содержимому
объекта. И пусть геттеры ничего особенного и не
делают, но зато полезную функцию проверки
выполняет метод-модификатор Set, и так
введением аксессоров можно добиться сокрытия
полей от несанкционированного доступа, оставив
только санкционированный.
Применяем инкапсуляцию!

С этой минуты договоримся помещать


поля в секцию private (исключение –
константы), а методы – в секцию public
(и только методы, реализующие
внутреннюю логику объекта,
недоступную в интерфейсе, будем
отправлять в private).
Инкапсуляция на практике

Представьте, что вы – разработчик dll-


библиотеки классов в большой компании,
и доступ к скрытым полям внутри класса
имеете только вы, в то время как ваши
коллеги могут использовать только
открытые элементы вашего класса. Раз
так, то и испортить по ошибке значения
скрытых полей они не смогут. Так и
работает концепция «чёрный ящик».
Пример кода
Ещё примеры

 Person с методами-аксессорами:

https://git.io/voqQq
 Cat с методами-аксессорами +
внутренняя логика:

https://git.io/voq5f
Задумайтесь!
Бывают сеттеры, которые не содержат никаких
дополнительных проверок. Не кажется ли вам,
что это какое-то лицемерие – в угоду
инкапсуляции делать поле приватным, а потом
создавать для него два публичных метода,
геттер и сеттер, при том что в сеттере
происходит самое обыкновенное присваивание
- именно то, что пользователь класса и так бы
сделал в своём клиентском коде? В чём тогда
смысл? * драматическая пауза *
Смысл инкапсуляции
Всю суть инкапсуляции можно передать одной фразой:
«изменяемость реализации, неизменность
интерфейса». Разработчик класса, определяя набор
публичных методов, чётко обозначает интерфейс
взаимодействия с классом. Пользователь этого
класса (другой программист) «привыкает» к
интерфейсу, и начинает его активно использовать в
своём клиентском коде. Со временем, даже если
разработчик библиотеки внесёт какие-либо изменения
в реализацию класса, или же добавит несколько
классов-наследников, пользователю класса свой
клиентский код переписывать заново не придётся.
Встроенные функции
Многофайловый проект
Массивы объектов класса
Вложенные объекты
Известно, что структура может быть
полем другой структуры, кроме самой
себя (хотя может содержать указатель
на себя). То же самое касается и
объектов:
Вложенные объекты
Чтобы создать объект, сначала должны быть
созданы все его части. Поэтому перед вызовом
конструктора объекта вызываются конструкторы
для объектов, являющихся его полями. Если они
сами состоят из объектов, то сначала вызовутся их
конструкторы и тд. Отсутствие у таких классов
конструктора без параметров или просто желание
создать поле с заданными параметрами требует
от языка программирования специального
механизма инициализации полей класса.
Инициализатор
Практика
 Добавить в класс Cat пять версий
конструкторов. Применить
делегирование. Создать пять объектов
класса разными способами.
 Поместить созданные объекты в массив.
Показать данные по каждому объекту
через цикл for-each.
 Вынести реализацию всех методов в .h-
файл

Вам также может понравиться