Уважаемые читатели!
Мы продолжаем знакомить вас с колонкой Криса Дейта, которую он на протяжении нескольких лет ведет в журнале DataBase Programming & Design. В сентябрьском номере г. Дейт размышляет относительно сути понятия инкапсуляции, которое многие считают одним из основных в объектно-ориентированном подходе. Во многом эта заметка опирается на вышедшую в начале лета 1998 г. книге Криса Дейта и Хью Дарвина "Основания объектно-реляционных баз данных: Третий манифест". Книга действительно очень интересна, хотя во много не является бесспорной. Аналогично, материал, краткое изложение которого представляется вашему вниманию, интересно и убедительно написан, но почти сразу с некоторыми идеями автора хочется спорить.
Может быть, когда-нибудь я напишу по этому поводу отдельную заметку, но мне хочется прямо сейчас кратко высказаться относительно рассуждений автора относительно возможных и реальных представлений и скалярных и генерируемых типов данных. Автор говорит, что для обеспечения возможности формулирования "непредвиденных" запросов с участием скалярных (или инкапсулированных) данных требуется обеспечить дополнительный набор "THE-операций", дающих возможность доступа к компонентам некоторого возможного представления значений и переменных соответствующего типа. С другой стороны, для данных генерируемых типов, по мнению автора, открыт доступ к компонентам представления. Но что значит "открыт" и к компонентам какого представления? Мне кажется, что (1) видимое пользователям представление генерируемого типа является "возможным" (в терминологии г. Дейта) и (2) те операции, которые существуют в любом генерируемом типе для доступа к его компонентам, реально являются "THE-операциями"; единственное отличие от скалярных типов состоит в том, что смысл и реализация этих операций определены в генераторе типа, т.е. они "наследуются" в генерируемом типе. На этом я пока остановлюсь, поскольку мое введение может оказаться длиннее заметки Криса Дейта.
Возможно, я просто привык читать г. Дейта, но мне это действительно доставляет удовольствие. Хотелось бы верить, что и вам тоже.
С уважением, Сергей Кузнецов
DataBase Programming & Design OnLine, September 1998
Оригинал статьи можно найти по адресу http://www.dbpd.com/9809date.html
Инкапсуляция широко воспринимается как ключевое свойство или премущество объектной технологии. Однако, по мнению автора, понятие инкапсуляции всегда несколько переоценивалось; более важным является проведение четкого различия между типами и представлениями. Как показывает название этого материала, автор считает инкаплуляцию саму по себе в чем-то похожей на "ржавую середку" [если читателей заинтересует происхождение такой странной метафоры, рекомендую заглянуть на Web-страницу журнала Red Herring - www.herring.com/about/lore.html (прим. С.Кузнецова)] и постарается далее это объяснить.
Говорят, что тип данных инкапсулирован, если экземпляры этого типа не имеют видимых пользователям компонентов. (Термин "экземпляр" используется как удобное, хотя и неуклюжее сокращение для "значение или переменная".) Например, в своей книге Smalltalk-80: The Language and It's Implementation (Addison-Wesley, 1983) Adele Goldberg и David Robson говорят: "Объект состоит из некоторой частной памяти и набора операций ... Публичные свойства объекта являются [спецификациями операций], составляющими его интерфейс... Частные свойства объекта - это набор [компонентов], составляющих его частную память".
Замечание: Автор предпочитает не использовать слишком часто термин "объект", потому что он нечеткий и часто приводит к неправильному пониманию сути. Ниже этот термин используется только в цитатах.
Как явствует из приведенного высказывания, пользователи Smalltalk оперируют экземплярами ("объектами") инкапсулированного типа посредством операций, явно определенных в связи с этим типом. Например, можно иметь тип CIRCLE и быть в состоянии вызывать операции, которые возвращают площадь - или длину окружности, или радиус (и т.д.) - любого заданного круга. Однако было бы незаконно утверждать, что круг имеет компонент площади, компонент длины окружности, компонент радиуса и т.д. Важным следствием этого является то, что мы не знаем и не должны знать, как круги представлены внутри системы; это представление доступно только через код, реализующий операции. Другими словами, для пользователей представляет интерес тип - это часть модели, - в то время как представление интересно только для реализации. В своем введении в объектные базы данных Stanley Zdonik и David Maier говорят: "Инкапсуляция [означает, что у каждого типа имеется] набор [операций и] представление ... которое выделяется для каждого его экземпляра. Это представление используется для сохранения состояния объекта. Только методы, реализующие операции объектов, имеют доступ к представлению, что дает возможность изменять представление не затрагивая оставшуюся часть системы. Требуется только переписать методы".(1)
Заметим, что к инкапсуляции можно относиться как к другому обличию известного понятия независимости данных. Если должным образом различать тип и представление, и если добиться скрытости представления, то можно изменять представление без потребности изменять прикладные программы. Нужно всего лишь изменить код, реализующий операции.
Возможно, теперь можно начать понимать, почему автор не считает понятие инкапсуляции слишком существенным. Основной смысл этого понятия состоит в том, что мы не должны беспокоиться о том, о чем нам не следует беспокоиться, а именно, о физическом представлении (называемом также действительным или внутренним представлением). Другими словами, как уже утверждалось, инкапсуляция, на самом деле, является логическим следствием различия между типом и представлением.
Последнее замечание по поводу термина. При подготовке этого материала автору пришлось просмотреть около 20 книг по объектной технологии и связанным темам. Удивительно, что ни в одной из них не удалось найти точное определение понятия инкапсуляции. (Лучшее разъяснение содержится в приведенной выше цитате из книги про Smalltalk; следует заметить, помимо прочего, что в этой книге вообще не используется термин "инкапсуляция", его нет даже в индексе.) Автор обнаружил, что некоторые авторы понимают под этим понятием физическое связывание определений представления данных и определений операций. Например, "Инкапсуляция - это понятие соединения обработки или поведения с экземплярами объектов, определенных в классах. Инкапуляция позволяет упаковывать вместе код и данные".(2) Но, по мнению автора, такая интерпретация термина приводит с перемешиванию модельных и реализационных вопросов. Пользователь не должен беспокоиться, у него не должно возникать поводов для беспокойства по поводу того, "упакованы ли вместе" код и данные. Убеждение автора состоит в том, что с точки зрения пользователя, т.е. с модельной точки зрения, инкапсуляция означает, что данные не содержат видимых пользователям компонентов и могут быть доступны только через посредство уместных операций.
Инкапсуляция вступает в некоторый конфликт с потребностью выполнять непредвиденные запросы. Инкапсуляция означает, что доступ к данным может быть произведен только через заранее определенные операции, а смысл непредвиденных запросов, почти по определению, состоит в том, что требуется доступ, способ которого невозможно предопределить. Например, пусть имеется тип данных POINT; предположим, что также имеется (предопределенная) операция для "взятия" (чтения или выборки) X-координаты заданной точки, но нет подобной операции для соответствующей Y-координаты. Тогда невозможно выполнить даже следующие простые запросы и множество подобных:
В Third Manifesto (3) Крис Дейт и Хью Дарвин для разрешения этого конфликта предлагают потребовать, чтобы для каждого заданного типа были определены операции, раскрывающие некоторое возможное представление экземпляров этого типа (авторы называют такие операции "THE_operators"). Для типа POINT, например, можно было бы определить операции THE_X и THE_Y, что позволило бы производить следующие действия:
Q := THE_Y (P) ; /* получить Y-координату точки P и присвоить ее Q */ Z := SQRT ( THE_X (P) ** 2 + THE_Y (P) ** 2 ) ; /* получить расстояние до точки P от точки (0,0) и присвоить его Z */
Таким образом, THE_X и THE_Y действительно раскрывают возможное представление - а именно, декартовы координаты X и Y - и обеспечивают возможность выполнять непредвиденные запросы с точками. Однако это не означает того, что внутри системы точки действительно представлены декартовыми координатами; это значит лишь то, что декартовы координаты являются возможным представлением. В реальном представлении могут использоваться декартовы координаты, полярные координаты R и U или что-нибудь совсем другое. Другими словами, THE_операции не нарушают инкапсуляцию и не подрывают независимость данных. Заметим, кстати, что типы данных DATE и TIME языка SQL представляют пример встроенных типов с раскрытием некоторых возможных представлений. Например, для дат раскрывается возможное представление с компонентами YEAR, MONTH и DAY. Хотя, вероятно, следует добавить, что эти "возможные" представления в SQL, к сожалению, близки к реальным представлениям; в SQL различие между типами и представлениями часто не является четким.
Еще одна причина, которая заставляет автора считать инкапсуляцию не настолько важным понятием, как это полагается в литературе, состоит в следующем. Некоторые типы являются совсем не инкапсулированными, и это никому не мешает! В частности, это относится к "генерируемым" типам, которые определяются с использованием генераторов типов, такие как ARRAY, LIST, TUPLE и RELATION. Рассмотрим для определенности RELATION. Предположим, что теперь мы имеем дело с relvar (relation variable) POINT:
VAR POINT ... RELATION { X ..., Y ... } ... ; /* для простоты типы атрибутов X и Y опущены */
Замечание: Определение сформулировано на языке Tutorial D, который определен в Third Manifesto для иллюстративных целей. Многоточия в определении поставлено вместо конструкций, не являющихся существенными для этого обсуждения.
В этом определении для указании (генерируемого) типа relvar используется генератор типов RELATION; это конкретный тип отношения, а именно:
RELATION { X ..., Y ... } /* снова для простоты типы атрибутов X и Y опущены */
И этот тип определенно не является инкапсулированным: у него имеются видимые пользователям компоненты - атрибуты X и Y. И именно наличие этих видимых пользователям компонентов позволяет выполнять над relvar POINT непредвиденные запросы; например, можно выполнить проецирование на атрибут Y, ограничение по условию "значение Y меньше пяти".
Заметим мимоходом, что похожие наблюдение содержатся в книге Mike Stonebraker про объектно-реляционные СУБД: "Базовые типы полностью инкапсулированы. Единственный способ манипулировать [экземпляром] базового типа состоит в том, чтобы выбрать его и выполнить функцию, принимающую [экземпляр этого типа] в качестве аргумента. В противоположность этому, составные типы полностью прозрачны. Можно видеть все поля, и они легко доступны в языке запросов. Конечно, промежуточная позиция заключается в том, чтобы допустить в составном объекте наличие публичных (видимых) полей и приватных (инкапсулированных). Этот подход используется в Си++". Однако следует добавить, что остается неясным, проводит ли Стоунбрейкер четкое различие между реальными и возможными представлениями, как это делается в The Third Manifesto.
Вернемся к основному аргументу. Тот факт, что типы отношений не искапсулированы, не означает потерю независимости данных! Например, в случае relvar POINT нет абсолютно никаких причин, по которым нельзя было бы физически представить эту relvar полярными координатами R и U, а не декартовыми координатами X и Y. (Вероятно, это невозможно сделать в сегодняшних продуктах SQL, но это недостаток продуктов. Сегодняшние продукты SQL вообще обеспечивают меньшую независимость данных, чем теоретически в состоянии обеспечить реляционная технология.) Другими словами, даже при использовании не инкапсулированных типов требуется четко различать типы и представления, и "отказ от инкапсуляции" не обязательно ведет к разрушению независимости данных.
В The Third Manifesto авторы требуют поддержки генераторов типа TUPLE и RELATION; в результате пользователи могут определять свои собственные типы кортежей и отношений. Кроме того, требуется, чтобы пользователи могли определять "простые" типы, такие как POINT, LENGTH, AREA, LINE и т.д., возможно, даже типы, подобные INTEGER, если система не обеспечивает их как встроенные типы. И термин "скалярный тип" относится к таким "простым" типам (после чего можно говорить о скалярных значениях, скалярных переменных и скалярных операциях).
Причины выбора термина "скаляр" заключались в следующем:
Заметим теперь, что термин "скалярный" означает в точности то же самое, что и "инкапсулированный", т.е. тип данных инкапсулирован тогда и только тогда, когда он скалярный. Поэтому, по мнению автора, в индустрии следовало бы использовать привычный термин "скалярный" без потребности употребления "инкапсулированный". Это помогло бы избежать некоторых двусмысленностей.
Замечание: Можно говорить, что скалярные (или, если это больше нравится, инкапсулированные типы) являются атомарными, поскольку не содержат видимых пользователям компонентов.
В этой заметке автор пытался показать, что термин "инкапсулированный" вызывает больше беспокойств, чем того стоит: