Книга: Основы объектно-ориентированного программирования

От абстрактных типов данных к классам

Итак, у нас имеется отправная точка - элегантная математическая теория для моделирования структур данных и, как мы только что видели, в целом - программ. Но наша цель - это архитектура ПО, а не математическая или даже теоретическая информатика! Не сбились ли мы с нашего пути? Отнюдь. При поиске подходящей модульной структуры, основанной на типах объектов, АТД предоставляют механизм описания высокого уровня, не связанный с особенностями реализации. Это приведет нас к фундаментальным структурам ОО-технологии.

Классы

В поиске, начатом в лекции 3, АТД будут служить непосредственной основой модулей. Точнее, ОО-система будет строиться (на уровне анализа, проектирования и реализации) как совокупность взаимодействующих, частично или полностью реализованных АТД. Основное понятие здесь - класс:

Определение: класс

Класс - это абстрактный тип данных, снабженный некоторой (возможно частичной) реализацией

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

Определение: отложенный и эффективный классы

Полностью реализованный класс называется эффективным (effective). Класс, который реализован лишь частично или совсем не реализован, называется отложенным (deferred). Всякий класс является либо отложенным, либо эффективным.

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

Как создавать эффективный класс

Рассмотрим вначале эффективные классы. Что нужно сделать для реализации АТД? Результирующий эффективный класс будет формироваться из элементов трех видов:

[x]. (E1) Спецификации АТД (множество функций с соответствующими аксиомами и предусловиями, описывающими их свойства).

[x]. (E2) Выбора представления.

[x]. (E3) Отображения из множества функций (E1) в представление (E2) в виде множества механизмов (или компонентов (features)), каждый из которых реализует одну из функций в терминах представления и при этом удовлетворяет аксиомам и предусловиям. Многие из этих компонентов будут методами - обычными процедурами, но некоторые могут появляться в качестве полей данных или "атрибутов" (это будет показано в следующих лекциях).

Например, для АТД STACK можно выбрать в качестве представления (шаг E2) решение, названное выше МАССИВ_ВВЕРХ, при котором каждый стек реализуется парой

<representation, count>,

где representation - это массив, а count - это целое число. При реализации функций (E3) у нас будут процедуры для функций put, remove, item, empty и new, выполняющие соответствующие действия. Например, функцию put можно реализовать программой вида

put (x: G)
is -- Втолкнуть x в стек.
-- (без проверки стека на возможное переполнение.)
do
count := count + 1
representation [count]:= x
end

Объединение элементов, полученных в пунктах (E1), (E2) и (E3), приведет к классу - модульной структуре объектной технологии.

Роль отложенных классов

В определении эффективного класса должна присутствовать полная информация о реализации (пункты E2 и E3). Если она хоть в чем-то неполна, то класс является отложенным.

Чем более "отложенным" является класс, тем он ближе к АТД, одетому в некоторую синтаксическую одежду, которая скорее поможет завоевать признание разработчиков ПО, чем математиков. Отложенные классы особенно полезны при анализе и проектировании:

[x]. При ОО-проектировании многие аспекты реализации будут опущены, проектирование должно сосредотачиваться на архитектурных свойствах высокого уровня - на том, какую функциональность обеспечивает каждый модуль системы, а не на том, как он это делает.

[x]. При постепенном продвижении к полной реализации будут добавляться все новые и новые ее свойства до тех пор, пока не будет получен эффективный класс.

Но на этом роль отложенных классов не завершается, даже в полностью реализованной системе можно часто обнаружить много таких классов. Кое-что следует из только что перечисленных применений: когда из отложенных классов получаются эффективные, то появляется желание сохранить их в качестве предков (в смысле наследования) эффективных классов как живую память о процессе анализа и проектирования.

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

У отложенных классов имеется также применение, полностью связанное с реализацией. Они служат для классификации групп связанных типов объектов, предоставляют некоторые наиболее важные многократно используемые модули высокого уровня, фиксируют общие свойства поведения многих вариантов и играют ключевую роль (вместе с полиморфизмом и динамическим связыванием) в обеспечении децентрализации и расширяемости программной архитектуры.

Несколько следующих лекций, в которых вводятся основные ОО-методы, будут сосредоточены на эффективных классах. Но при этом следует помнить о понятии отложенного класса, чья важность будет расти по мере овладения всей мощью ОО-метода.

Абстрактные типы данных и скрытие информации

Особенно интересным следствием ОО-политики, в которой модули основаны на реализациях АТД (классах), является то, что она дает ясный ответ на вопрос, который остался нерешенным при обсуждении скрытия информации: как нам следует разделять общедоступные и скрытые свойства модуля - видимую и невидимую части айсберга?


Рис. 6.6.  АТД вид модуля при скрытии информации

Если модуль является классом, полученным из АТД, то ответ ясен. Из трех частей, вовлеченных в эту эволюцию, E1- спецификация АТД, является открытой, а E2 и E3 - выбор представления и реализация функций АТД в терминах этого представления - должны быть закрытыми (секретными). Когда мы начнем строить классы, то столкнемся еще с четвертой частью, также секретной, - вспомогательными свойствами, необходимыми только для внутренних нужд этих программ.

Таким образом, использование абстрактных типов данных в качестве источника модулей дает нам практичное, однозначное указание для применения скрытия информации в наших проектах.

Переход к более императивной точке зрения

Переход от АТД к классам включает существенное изменение стилистики: введение изменений и императивных аргументов.

Как вы помните, спецификация абстрактных типов данных не описывает явно изменений, т. е., используя термин из теоретической информатики, является аппликативной. Все свойства АТД моделируются как математические функции, это относится к конструкторам, запросам и командам. Например, операция вталкивания для стеков моделируется функцией-командой:

put: STACK [G] ? G STACK [G],

задающей операцию, которая возвращает новый стек, а не изменяет существующий.

Классы отказываются от чисто аппликативной точки зрения на функции и переопределяют команды как операции, которые могут изменять объекты. Например, операция put будет определена как процедура, которая получает некоторый элемент типа G (формальный параметр) и модифицирует стек, вталкивая новый элемент на его вершину, не создавая нового стека.

Такое изменение стиля отражает императивные настроения, преобладающие при разработке ПО. (В качестве синонима слова "императивный" иногда используется термин "операционный"). При этом потребуется изменять аксиомы АТД. Аксиомы стеков A1 и A4, которые имели вид

[x]. (A1) item (put (s, x)) = x

[x]. (A4) not empty (put (s, x))

превратятся в императивной форме в предложение, называемое постусловием программы (routine postcondition), вводимое ключевым словом ensure (обеспечивает):

put (x: G) is
-- Втолкнуть x на вершину стека
require
... Предусловие (если таковое имеется) ...
do
... Соответствующая реализация (если известна) ...
ensure
item = x
not empty
end

Здесь постусловие объясняет, что результатом вызова программы put значение item будет равно x (втолкнутому элементу), а значение empty будет ложно.

Другие аксиомы спецификации АТД приводят к утверждению, известному как инвариант класса. Постусловия, инварианты класса и другие перевоплощения предусловий и аксиом АТД мы рассмотрим во время обсуждения утверждений и проектирования по контракту (п. 11.10 "Связь с АТД").

Назад к тому, с чего начали?

Если вы внимательно следили, начиная с лекции о модульности, за главной линией рассуждений, которая привела нас к абстрактным типам данных, а затем и к классам, то сейчас, быть может, вы будете удивлены. Поставив целью получить по возможности наилучшую модульную структуру, мы пришли к тому, что объекты, точнее - типы объектов, будут лучшей основой для модулей, чем их традиционные соперники - функции. Это привело к следующему вопросу: как описать эти типы объектов. Но, когда мы на него ответили: описывать нужно в виде абстрактных типов данных (и их заменителей на практике - классов), то оказалось, что нужно основывать описание данных на ... применяемых к ним функциях! Не получился ли у нас порочный круг?

Нет. Типы объектов, представлямые АТД и классами, остаются неизменной основой модуляризации.

Неудивительно, что и объектный, и функциональный аспект должен проявиться в окончательной архитектуре системы: никакое описание вопросов ПО не может считаться полным, если в нем опущена одна из этих компонент. Фундаментальное различие ОО-методов и старых подходов состоит в распределении ролей: типы объектов - безусловные победители при выборе критериев для построения модулей. Функциям достается только роль их слуг.

При ОО-декомпозиции никакая функция не существует сама по себе - каждая функция прикреплена к некоторому типу объектов. Это относится и к уровню проектирования, и к уровню разработки: никакое свойство не существует само по себе, каждое из них прикреплено к некоторому классу.

Конструирование объектно-ориентированного ПО

Мы уже давали определение конструирования ОО-ПО: будучи весьма общим, оно представляет метод следующим образом: "основывать архитектуру всякой программной системы на модулях, полученных из типов объектов, с которыми оперирует система". Придерживаясь рамок этого определения, мы можем дополнить его теперь более техническим определением:

Конструирование объектно-ориентированного ПО (определение 2)

Конструирование ОО-ПО - это построение программной системы как структурированной совокупности реализаций (возможно частичных) абстрактных типов данных.

Это определение будет нашим рабочим определением. Все его компоненты являются важными:

[x]. В основе лежит понятие абстрактного типа данных.

[x]. Для конструирования программ нам нужны не сами по себе АТД (как математическое понятие), а реализации АТД - программистское понятие.

[x]. При этом эти реализации не обязаны быть полными, оговорка "возможно частичные" позволяет использовать и отложенные классы, включая, как крайний случай, полностью отложенный класс без какой-либо реализации.

[x]. Система представляет собой совокупность классов без выделения какого-либо главного или ответственного класса или головной программы.

[x]. Эта совокупность является структурированной благодаря двум отношениям между классами: "быть клиентом" и наследованию.

Оглавление книги


Генерация: 0.375. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз