Анимация
JavaScript
|
Главная Библионтека Определение слова "капсула", в свою очередь, дает нам указания о том, каким должен быть хороший интерфейс класса (здесь приведены не все возможные значения этого слова): капсула - 1. структура наподобие мембраны или мешка, окружающая часть органа или орган в целом; 2. закрытый контейнер, содержащий споры или семена; ... 4. желатиновая оболочка вокруг лекарственного препарата; ... металлическая пломба;... 6. оболочка вокруг некоторых микроскопических организмов; ... 9. небольшое герметизированное помещение для летчика или космонавта. ... Обратите внимание на однотипность определений - охватывает, окружает, упаковывает, запечатывает... Хороший интерфейс класса скрывает внутренние детали его реализации, представляя внешнему миру только "лицо", которое отделено от "внутренностей". Поскольку капсула охватывает одну связанную фуппу подобъектов, се интерфейс также должен быть связанным - все его части должны иметь непосредственное отношение друг к другу. Хороший интерфейс класса должен быть полным и не показывать вовне никаких деталей внутренней реализации класса. Он работает как герметичная оболочка или как брандмауэр (времени компиляции, времени выполнения или и того, и другого одновременно), так что внешний код не может зависеть от внутренней реализации класса, и любые изменения внутреннего строения и реализации класса никак не влияют на внешний используюший этот класс код. Бактерия, оболочка которой не замкнута, проживет недолго... Хороший интерфейс класса защищает его "внутренности" от неавторизованного доступа и манипуляций. В частности, главная задача интерфейса - гарантировать, что любое обращение к внутренним структурам класса и работа с ними сохраняют инварианты класса. Основной способ убить бактерию (как и, скажем, человека) - использование устройств, которые нарушают целостность с с внешней и/или внутренней капсул. На микроуровне это могут быть химические реактивы, ферменты или организмы (возможно, даже наномеханизмы), способные проделать в оболочке соответствующие отверстия. На макроуровне предпочтение отдается ножам и автоматам... Место инкапсуляции в объектно ориентированном программировании Насколько она важна в обьектно-ориешированном проектировании и программировании? Инкапсуляция - основная концепция объектно-ориентированного профаммирования. Точка. Прочие объс ктн о - о ри с нти ро ван и ые методы - такие как сокрытие данных, наследование и полиморфизм - важны в первую очередь потому, что они выражают частные случаи инкапсуляции. • Инкапсуляция практически всегда влечет за собой сокрытие данных. • Полиморфизм времени вьтолнения, использующий виртуальные функции, более полно отделяет интерфейс (представленный базовым классом) от реализации (которая представляется производным классом, который может даже не существовать в тот момент, когда разрабатывается код, который будет его использовать). • Полиморфизм времени компиляции, использующий шаблоны, полностью отделяет интерфейс от реализации, так как любой класс, удовлетворяющий некоторым требованиям, может быть использован шаблоном. Используемые классы не обязаны быть связаны наследованием или каким-либо иным отношением. Инкапсуляция - не всегда сокрытие данных, но сокрытие данных - частный случай инкапсуляции. Инкапсуляция - не всегда полиморфизм, но полиморфизм - всегда форма инкапсуляции. Объектная ориентированность часто определяется следующим образом: связывание в единое целое данных и функций, которые оперируют с этими данными. Такое определение справедливо с определенными допущениями - поскольку оно исключает свободные функции (не являющиеся членами), которые являются логической частью класса (такие как оператор « С++). Кроме того, данное определение не подчеркивает другой существенный аспект объектной ориентированности, а именно одновременное отделение данных от вызывающего кода посредством интерфейса, представляющего собой набор функций, которые работают с этими данными. Этот дополнительный аспект подчеркивает слабое связывание внешнего кода и данных, а также то, что предназначение собранных функций - формирование интерфейса, защищающего данные. Кратко можно сказать, что объектно-ориентированное п ро грам мировая и е состоит в отделении интерфейсов от реализации, что обеспечивает, с одной стороны, высокую степень единства кода и данных, и низкую степень связывания - с другой. Разработчики программного обеспечения сталкивались с задачей обеспечения такой связи функций и данных задолго до "открытия" объектов. Эти концепции относятся к управлению зависимостями, которое представляет собой одно из ключевых понятий в современном проектировании профаммного обеспечения, в особенности для больших систем (см. [Martin95] и другие статьи Мартина (Martin), датированные 1996 годом в [ObjectMentor], в особенности те из них, в заголовке которых есть слово "Principle"). Открытые, закрытые или защищенные данные? 2. В каких случаях (если таковые имеются) иестатические члены данных должны быть открытыми (public), защишенны.чи (protected) и закрытыми (private)? Выразите ваш ответ в виде рекомендации программисту. Обычно мы начинаем с того, что рассматриваем правило, а уже затем - исключения из него. Давайте в этот раз нарушим порядок и сначала рассмотрим исключения, а потом перейдем к правилу. Единственное исключение из общего правила (которое будет приведено далее) - это когда все члены класса (как функции, так и данные) открытые, как в случае структуры в С. В этом случае класс - не вполне полноценный класс со своим интерфейсом, поведением и инвариантами - словом, это не класс, а набор данных. Такой "класс" - просто удобное объединение объектов в единое целое, и это вполне нормальное явление, в особенности с точки зрения обратной совместимости с программами С, которые работают со структурами. За исключением этого частного случая, все члены-данные должны быть закрытыми. Открытые данные - нарушение инкапсуляции, поскольку они позволяют вызывающему коду непосредственно работать со внутренними объектами. Это требует высокой степени доверия! В конце концов, в реальной жизни я вряд ли разрешу кому-либо иметь дело непосредственно с моими внутренностями (скажем, позволив копаться внутри живота), поскольку при этом очень легко, пусть даже непреднамеренно, допустить ошибку, которая может, мягко говоря, повредить моему организму. Другое дело - косвенная работа через открытый интерфейс, когда я могу сам решить, что и как делать (например, получив бутылку с этикеткой "Выпей меня", я сам. опираясь на собственные ощущения от содержимого бутылки и свой здравый смысл, решу, пить ли содержимое бутылки или все же с его помощью вымыть машину). Конечно, есть люди, достаточно квалифицированные, чтобы копаться в моих внутренностях непосредственно (например, хирург), но даже в этом случае а) это бывает редко, б) я сам решаю, нужна ли мне помощь хирурга и в) я сам решаю, какому хирургу я могу доверить такие драгоценные для меня внутренности. Аналогично, в большинстве случаев вызывающий код не должен работать со внутренней организацией класса непосредственно (например, просматривая или изменяя члены-данные), поскольку при этом очень легко непреднамеренно совершить неверные действия; в лучшем случае внешний код должен работать с внутренними данными косвенно, при помощи открытого интерфейса класса, когда класс может сам решить, что следует делать с переданными ему параметрами (рассмотренный пример БутылкаСвыпей меня")) на основании знаний и суждений автора данного класса. Конечно, определенный код может быть специально предназначен для непосредственной работы с внутренней организацией класса (обычно такой код должен быть фyнкциeй-uleнoм класса, но, например, operator« делать членом не рекомендуется), но даже в таком случае: а) это бывает редко, б) класс сам решает, объявлять ли кого-либо другом класса или нет, и в) класс сам решает, какой именно код заслуживает достаточного доверия, чтобы его можно было объявить другом и дать ему доступ к внутренней организации класса. Словом, открытые данные есть зло (кроме данных в структурах в стиле С). Аналогично, защищенные данные - такое же зло, но на этот раз без всяких исключений. Подождите минутку, - может возразить кто-то из читателей, - я согласен с вами, когда вы говорите об открытых данных, но почему вы считаете таким же злом защищенные данные?" Погому что тс же аргументы, которые были перечислены для открытых данных, применимы и к защищенным данным, которые тоже являются частью интерфейса: защищенный интерфейс также является интерфейсом ко внешнему коду, только к меньшему его подмножеству - а именно коду производных классов. Почему в этом случае нет исключений? Потому что защищенные данные не могут быть просто сгруппированными данными; если бы это было так, то ими могли бы воспользоваться только производные классы, а какой в этом смысл? Об истории этого вопроса и о том, почему человек, ратовавший за наличие защищенных данных в языке, теперь считает это плохой идеей, можно прочесть в книге [Stroustrup94]. > Рекомендация Всегда делайте все члены-данные закрытыми. Единственное исключение - структура в стиле С, которая не предназначена для инкапсуляции чего бы то ни было, и все члены которой открыты. Преобразование в общем случае Давайте теперь докажем, правило "все члены-данные всегда должны быть закрытыми" от противного - предположим, что справедливо обратное утверждение (что имеются ситуации, когда public/protected члены-данные могут быть подходящим решением) и покажем, что в каждом таком случае данные не должны быть ни открытыми, ни защищенными. пример 17-2(а): не закрытые данные (плохо) class X { ... public: Tl tl ; protected: t2 t2 ; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [ 32 ] 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |