Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
Селективный экспорт и скрытие информации
До сих пор все компоненты класса были доступны всем потенциальным клиентам. Это, безусловно, не всегда приемлемо, поскольку скрытие информации является важным элементом построения последовательной и гибкой архитектуры.
Рассмотрим способы скрытия компонент от всех или некоторых клиентов. Данный раздел содержит лишь введение в нотацию - подробному рассмотрению интерфейсов классов посвящена одна из последующих лекций (лекция 5 курса "Основы объектно-ориентированного проектирования"). В примерах для простоты будут рассматриваться только именованные компоненты, однако все изложенные ниже соображения справедливы и для компонент-операций.
Неограниченный доступ
По умолчанию все компоненты доступны для всех клиентов. Для класса
class S1 feature
f ...
g ...
...
end
компоненты f, g, ... доступны всем клиентам S1. Это означает, что если в классе C объявлена сущность x класса S1, то вызов
x.f ...
является допустимым, если выполнены все другие условия корректности вызова f.
Ограничение доступа клиентам
Для ограничения доступа клиентов к некоторой компоненте h, будет использована возможность включения в объявление класса двух или более разделов feature. Объявление будет выглядеть следующим образом
class S2 feature
f ...
g ...
feature {A, B}
h ...
...
end
Компоненты f и g по-прежнему доступны всем клиентам. Компонент h доступен только для классов A и B, а также их потомков (прямых или косвенных). Это означает, что для некоторого x типа S2 следующий вызов
x.h
является допустимым только в исходных текстах классов A, B или одного из их потомков.
В особом случае, когда необходимо скрыть компонент i от всех клиентов, можно объявить его экспортируемым пустому списку клиентов (Не рекомендуемый стиль (см. ниже S5).):
class S3 feature { }
i ...
end
В этом случае любой вызов x.i(...) недопустим. Единственная возможность обращения к i - неквалифицированный вызов
i (...)
в тексте подпрограммы класса S3 или его потомков. Такой механизм обеспечивает полное скрытие информации.
Возможность полного скрытия компонента от клиентов доступна во многих ОО-языках, а вот механизм селективного ограничения доступа, проиллюстрированный на примере h, к сожалению, практически не поддерживается. Подобный более тонкий контроль доступа необходим достаточно часто. Вопрос о важности селективного экспорта обсуждается в дискуссии в конце лекции.
В примерах последующих лекций мы столкнемся с различными примерами селективного экспорта и рассмотрим его методологическую роль при разработке интерфейсов.
Стиль объявления скрытых компонент
Использованный выше стиль объявления скрытой компоненты i не слишком удачен. Это хорошо видно в следующем примере (Не рекомендуемый стиль (см. ниже S5).)
class S4 feature
exported ...
feature {}
secret ...
end
где secret является скрытой компонентой, а exported - общедоступной. Разница в написании feature {} с пустым списком в скобках и feature без всяких скобок едва заметна. Гораздо разумнее вместо пустого использовать список, содержащий единственный класс NONE (Рекомендуемый стиль.)
class S5 feature
... exported ...
feature {NONE}
... secret ...
end
Класс NONE является базовым библиотечным классом и обсуждается далее в связи с наследованием. По определению он не может иметь потомков и нельзя создать его экземпляр. Таким образом, компонент, экспортированный классу NONE, фактически является скрытым. Между объявлениями S4 и S5 нет принципиальной разницы, однако во втором случае исходный текст становится более понятным и удобочитаемым. Именно такой стиль объявления скрытых компонент будет использоваться далее в этой книге.
"Внутренний" экспорт
Рассмотрим объявление класса
indexing
замечание: "Ошибочное объявление (объяснение см. ниже)"
class S6 feature
x: S6
my_routine is do ... print (x.secret) ... end
feature {NONE}
secret: INTEGER
end -- class S6
Наличие в объявлении класса атрибута x типа S6 и вызова x.secret делает его собственным клиентом. Но такой вызов недопустим, так как компонент secret скрыт от всех клиентов! Тот факт, что неавторизованным клиентом является сам класс S6, нечего не меняет - объявленный статус secret делает недопустимым любой вызов вида x.secret. Всякие исключения нарушают простоту сформулированного правила.
Есть простое решение: написать вместо feature {NONE} предложение feature {S6} , экспортируя компоненту самому себе и своим потомкам.
Необходимо отметить, что подобный прием необходим, только если в тексте класса присутствует квалифицированный вызов аналогичный print (x.secret). Очевидно, что неквалифицированный вызов secret в инструкции print (secret) допустим без дополнительных ухищрений. Все компоненты, объявленные в данном классе, могут использоваться в подпрограммах данного класса и его потомков. Только при наличии квалифицированных вызовов приходится экспортировать компонент самому себе.
Собираем все вместе
После введения в базовые механизмы ОО-вычислений настало время ответить на вопрос, каким образом можно построить исполняемую систему на основе отдельных классов.
Общая относительность
Удивительно, но все приведенные до сих пор описания того, что происходит во время выполнения, были относительными. Результат выполнения подпрограммы всегда связан с текущим экземпляром, который в исходном тексте класса неизвестен. Можно попытаться понять действие вызова, только принимая во внимание цель этого вызова, например p1 в следующем примере:
p1.translate (u, v)
Однако возникает следующий вопрос: что в действительности обозначает p1? Ответ опять относителен. Предположим, приведенный вызов присутствует в тексте некоторого класса GRAPHICS, а p1 это атрибут GRAPHICS. Тогда очевидно, что в этом случае p1 фактически означает Current.p1. Но это не ответ на поставленный вопрос, так как неизвестно, что представляет собой объект Current в момент вызова! Другими словами, теперь необходимо установить клиента, вызывающего подпрограмму класса GRAPHICS, в которой используется наш вызов.
Большой Взрыв
Рассмотрим произвольный вызов. Понимание смысла, происходящего в процессе произвольного вызова, позволит полностью разобраться в механизме ОО-вычислений. Используем сформулированный ранее принцип вызова компонентов:
[x]. (F1) Любой элемент программы может выполняться только как часть вызова подпрограммы.
[x]. (F2) Каждый вызов имеет цель.
Любой вызов может принимать одну из следующих форм:
[x]. неквалифицированная: f (a, b, ...);
[x]. квалифицированная: x.g (u, v, ...) .
Аргументы в обоих случаях могут отсутствовать. Вызов размещен в теле подпрограммы r и может выполняться только как часть вызова r. Предположим, что известна цель этого вызова - некий объект OBJ. Тогда можно легко установить цель этого вызова - t. Возможны четыре варианта, первый из которых относится к неквалифицированному вызову, а остальные - к квалифицированному:
[x]. (T1) Для неквалифицированного вызова t это просто OBJ.
[x]. (T2) Если x это атрибут, то x - поле объекта OBJ-имеет значение, которое, в свою очередь, присоединено к некоторому объекту - он и есть t.