Инкапсуляция подразумевает
объединение и защиту кода и данных в некоторой
сущности: объекте или модуле. Как правило,
идея объединения кода и данных не вызывает
серьёзных возражений. Это отчасти обусловлено
тем, что исследователи понимают, что невозможно
провести чёткой грани между кодом и данными:
код обрабатывает данные, а данные управляют
кодом. То или иное значение данных может
вызвать разные ветки кода, но те или иные
ветки кода могут, так или иначе, изменить
данные. Идея объединения кода и данных
восходит к модульному программированию
(см. Письмо 01) и не является «изобретением»
объектной технологии.
В отличие от идеи объединения
кода и данных, идея защиты имеет разные
толкования. Вот точка зрения на инкапсуляцию
Г. Буча: «Скрытие информации – понятие
относительное: то, что спрятано на одном
уровне абстракции, обнаруживается на другом
уровне. Забраться внутрь объектов можно;
правда, обычно требуется, чтобы разработчик
класса-сервера об этом специально позаботился,
а разработчики классов-клиентов не поленились
в этом разобраться. Инкапсуляция не спасает
от глупости; она, как отметил Страуструп,
«защищает от ошибок, но не от жульничества».
Разумеется, язык программирования тут
вообще ни при чём; разве что операционная
система может ограничить доступ к файлам,
в которых описаны реализации классов.
На практике же иногда просто необходимо
ознакомиться с реализацией класса, чтобы
понять его назначение, особенно, если
нет внешней документации» [ГБ. стр. 66].
Прежде чем соглашаться с подобной точкой
зрения следует рассмотреть истоки проблемы.
Для чего вообще необходима
инкапсуляция? Для того чтобы защитить
реализацию класса. Если не обеспечивается
должная защита, то прикладной программист,
использующий данный класс, имеет возможность
«привязаться» к конкретной реализации.
В этом случае теряется возможность простой
модификации не только данного класса,
но и всей системы в целом. Практически
любое изменение может катастрофическим
образом сказаться на работоспособности,
как других частей системы, так и прикладных
программ. Однако такое положение противоречит
изложенному выше требованию обеспечения
модифицируемости. Чтобы осознать важность
этого положения можно провести простой
эксперимент. Предположим, что существует
возможность обращаться к любому оператору
подпрограммы из внешнего программного
обеспечения. Это нарушает инкапсуляцию
кода подпрограммы, но одновременно утрачивается
и возможность модификации самой подпрограммы.
Теперь нельзя изменить реализацию кода
по той причине, что какое-то внешнее программное
обеспечение могло обращаться к одному
из удаляемых или изменяемых операторов.
Аналогичная картина складывается при рассмотрении
инкапсуляции на уровне объектов (классов).
Нарушая инкапсуляцию, мы обязательно утрачиваем
модифицируемость, не зависимо от того,
о каких сущностях идёт речь. Таким образом,
«скрытие информации» является таким же
относительным понятием, как и требования
гибкости и модифицируемости.
С другой стороны инкапсуляция
прямо связана и с требованием повышения
надёжности систем (см. Письмо 01). Без
соблюдения инкапсуляции различные модули
и объекты могут произвольно менять данные
других модулей или объектов, что неизбежно
и негативно скажется на надёжности программного
обеспечения. Можно рассмотреть простой
пример, который показывает важность соблюдения
инкапсуляции. Предположим, что нам необходимо
разработать файловую подсистему.
Выделим структуру данных,
которая необходима для работы с файлом,
и создадим набор интерфейсных функций,
которые будут осуществлять взаимодействие
между программами и файлами. Очевидно,
что набор интерфейсных функций мал. Как
правило, он состоит из следующего перечня:
создать файл, удалить файл, переименовать
файл, открыть файл, закрыть файл, прочитать
некоторое произвольное число байт из файла,
записать некоторое произвольное число
байт в файл, переместить указатель файла
в новую позицию. Такой интерфейс позволил
избежать необходимости в прямом доступе
к внешним накопителям и, как следствие,
снизить вероятность повреждения информации.
В результате инкапсуляции структур данных
и кода в одном модуле значительно упростился
доступ к информации, и исчезла необходимость
в дублировании кода доступа для каждой
программы. Однако важно и то, что благодаря
инкапсуляции работа с файлами стала намного
надёжнее и безопаснее.
Интересно и другое, а именно
то, что появилась возможность абстрагироваться
не только от физических параметров внешних
носителей, но от самих носителей! Собственно,
какая разница, куда пишется информация:
на внешний носитель вроде «жёсткого диска»
или на иное устройство? Никакой! Следовательно,
в виде файла вывода может выступать дисплей,
принтер или другое устройство. Таким образом,
в системе становится доступной возможность
потокового ввода-вывода.
В книге Г. Буча инкапсуляция
рассматривается в тесной связи с абстракцией,
что абсолютно справедливо. Но, к сожалению,
там ничего не сказано о том, что практическую
пользу от инкапсуляции можно получить
в полном объёме только тогда, когда мы
создаём некоторый сервис, но не программу.
Представьте, что инкапсуляция файла выполнена
в одной единственной программе. Много
ли пользы от этого?! Нет. Если же файловый
сервис вынести в некоторое межпрограммное
пространство, то возможности становятся
несравненно больше, не говоря уже о том,
что можно организовать работу с одним
и тем же файлом нескольких программ одновременно.
Теперь можно сделать вывод
о том, что частичная инкапсуляция – это
миф. Инкапсуляция либо есть, либо её нет.
Очень часто можно слышать разговоры о
том, что для пользователей некоторого
класса или модуля должен поддерживаться
один уровень инкапсуляции, а для разработчиков,
которые создают собственные классы или
модули на основе данного, например, с
помощью наследования, уровень инкапсуляции
должен быть другим. Такой подход не кажется
верным. С точки зрения создателей модуля
или класса совершенно безразлично, почему
новая реализация их программного обеспечения,
полностью соответствующая заявленной спецификации,
приводит к сбоям в работе другого программного
обеспечения.
|