Фундаментальные принципы объектно ориентированного проектирования (Часть 2): Инкапсуляция

Вольный перевод статьи Fundamental Object Oriented Design principles (Part 2): Encapsulationopen in new window.

Рассмотрим принцип Инкапсуляции используемый объектно ориентированными языкми программирования.

Это вторая часть серии об объектно ориентированном проектировании. Чтобы полностью понять почему изучение шаблонов проектирования важно, мы должны начать с рассмотрения того что такое хороший объектно ориентированный дизайн приложения.

ОО-языки предоставляют уникальные возможности, которые содействуют хорошему дизайну приложения. Рассматриваемые принципы проектирования могут быть применены и для не объектно ориентированных языков, но это потребует больших усилий.

Эта статья предполагает что вы знакомы как минимум с одним ОО языком программирования. C++, Delphi, C# и Java являются примерами объектно ориентированных языков. В отличии от них JavaScript основан на объектах, в нём используется прототипирование вместо классов. Настоящий ОО язык программирования имеет классы, объекты и очень часто интерфейсы.

Что такое Инкапсуляция?

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

Объекты могут быть однозначно идентифицированы ссылкой

Концепция объекта может быть трудна для понимания тех кто пришёл с процедурных или функциональных языков. ООП-разработчики часто сталкиваются с аргументам о том что ООП не предоставляет ничего нового с тем что есть в процедурных языках. Позвольте мне объяснить Инкапсуляцию в форме разговора:

Я: У вас есть машина? Вы: Да Я: Где она сейчас Вы: Снаружи, на парковке Я: Какого она цвета? Вы: Синего Я: Описание сужается примерно до четверти всех автомобилей. Назовите больше деталей. Вы: Это Honda Accord, 2010 года Я: Две машины подходят под это описания. Может быть есть что-то более специфичное? Вы: Мой номер й454ок Я: Итак, это ваша машина? Вы: Да Я: Вы уверены? Вы: Да Я: Почему? Вы: Она соответствует всем требованиям о которых я знаю. Я: Если ты я заменил ваш автомобиль на автомобиль с тем же цветом, маркой, моделью и номерным знаком был бы он вашим? Вы: Хорошо... нет. Я не смог бы сказать сразу что это не моя машина, но я понял бы это когда бы сел в неё и попробовал завести своим ключом. Я: Что если бы она имела идентичный замок? Вы: Вы очень раздражающий, вы знаете об этом? Я: Да, да. Могу я задать последний вопрос? Вы: Хорошо, но вы проверяете моё... Я: Действительно ли вы можете знать что машина ваша без проверки каждого атрибута? Вы: Да Я: Как вы можете сделать это? Извините, Я думаю это не был мой последний вопрос. Вы: Просто я должен смотреть на неё всё время.

Объект по сути является просто ссылкой на структуру которая инкапсулируют и данные и специфичные для этого типа функции. Два разных объекта будут иметь две разных ссылки. Эта ссылка эквивалентна постоянному наблюдению за машиной. Изменение машины отменяет ссылку и изменяет лежащие в её основе данные. Как вы увидите дальше, в действительности мы не хотим этого, и объект предоставляет защиту от опасного прямого доступа. До сих пор вы можете сказать, что тоже самое вы можете сделать с указателями на структуры. Вы можете быть правы в том что касается C++, так как C++ структуры и классы очень похожи исключая видимость членов по умолчанию. В языке вроде Delphi объекты всегда находятся в куче, в то время как записи (record) могут находится в стеке и в куче. Границы между записями и объектами размыты, но только потому что сейчас у записей появилось больше специфичных для объектов функций. Есть большое отличие: на объекты мы ссылаемся через переменную объявленную как класс объекта или как класс одного одного из предков. Мы также можем ссылаться на объект через один (или больше) интерфейс которые поддерживает его класс. Это обеспечивает безопасный доступ к типу за ссылкой.

Объекты хранят данные и функции вместе

Что делает объект большим чем просто указатель на структуру данных в памяти? Это совмещение функциональности и данных близко друг к другу. Давайте вернёмся на парковку:

Я: Что происходит когда вы поворачиваете ключ зажигания? Вы: Машина заводится Я: Как? Вы: Я не знаю, я делаю это поворачивая ключ. Я: И затем? Вы: Я включаю передачу, нажимаю на газ и еду? Я: Но как в действительности машина работает? Вы: Меня заботит только то что она работает.

Внезапно наше различие становится ещё более ясным. Мы можем напрямую управлять машиной: повернуть ключ, включить передачу, нажать на газ. Если мы смоделируем это в процедурном стиле мы можем сделать процедуры для каждого действия с параметром — указателем на структуру данных "машина". В случае ООЯП процедуры которые работают непосредственно на объекте (методы) легко найти и они прямо обявляются в обявлении класса (class definition) класса.

Объекты позволяют скрыть внутреннюю функциональность и приватные данные

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

В C++ определение класс обычно находится в заголовочных файлах (.h, .hpp, .hxx), а реализация в файлах с кодом (.c, .cpp, .cxx). Также в Delphi, определение класса обычно находится в секции интерфейса модуля, а код методов находится в секции реализации. Другие модули, которые ссылаются на текущий модуль (через конструкцию uses) имеют доступ только к секции интерфейса текущего модуля. В Java\C# определение и реализация класса объединены, но потребители класса имеют ограниченную видимость. Таким образом от C++ к Delphi и затем к C# мы видим постепенную уменьшение физического разделения интерфейса и реализации через файлы, но это не так важно как звучит. Это не влияет на видимость снаружи класса.

Объекты предоставляют права доступа

В большинстве объектно ориентированных языках есть концепция секций с разным уровнем доступа. В Delphi имеются следующие области видимости

  • strict private
  • private
  • strict protected
  • protected
  • public
  • published

Подробнее в посте Области видимости элементов классов Delphiopen in new window.

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

TArc = class
private
  FRadius: double;
  procedure SetRadius(const AValue: double);
public
  property Radius: double read FRadius write SetRadius;
end;
1
2
3
4
5
6
7

В итоге

Инкапсуляция организует код и контролирует доступ. Она позволяет нам ссылаться на группу связанных данных по ссылке и оперировать ими только через чётко обозначенные методы и свойства.

Последниее изменение: 24.08.2023, 06:42:55