Бертран Мейер - Основы объектно-ориентированного программирования
Вопросы именования могут вначале показаться поверхностными - "косметическими", как иногда говорят программисты. Но не забывайте, что одна из наших конечных целей - создать основу для мощных, профессиональных библиотек программных компонент, допускающих повторное использование. Такие библиотеки будут содержать десятки тысяч доступных операций. Без их систематической и ясной номенклатуры и разработчики, и пользователи этих библиотек быстро потонут в потоке специальных и несравнимых имен, что создаст сильное (и не имеющее оправдания) препятствие к масштабному повторному использованию.
Таким образом, вопросы именования - это не косметика. Хорошее, допускающее повторное использование ПО - это ПО, которое предоставляет пользователям соответствующий набор функций и предоставляет их под правильными именами.
Имена, использованные здесь для операций стеков, являются частью соглашений об именовании, которых мы придерживаемся во всей книге.
Можно ли обойтись без абстракций?
В разработке программного обеспечения, как и в других научных и технических дисциплинах, плодотворная идея после того, как ее раскрыли, может показаться очевидной, даже если потребовалось много времени, чтобы она возникла. Сначала зачастую появляются плохие и запутанные (что часто одно и то же) идеи, и требуется время, чтобы более простые и элегантные заняли их место.
Это замечание справедливо и для абстрактных типов данных. Хотя хорошие разработчики ПО всегда с пользой применяли абстракцию (вследствие хорошего образования или просто интуитивно), многие из существующих ныне систем были разработаны без учета этой цели.
Однажды я невольно провел один небольшой эксперимент, который хорошо иллюстрирует такое состояние дел. Как-то, когда в курсе, который я читал, пришло время готовить проекты, я решил предоставить студентам нечто вроде анонимного рынка, куда бы они могли помещать шутливые объявления о продаже программных модулей, не раскрывая их источников. (Идея, хорошая или не очень, состояла в том, чтобы процесс выбора модулей происходил только на основе точных спецификаций их возможностей.) Почтовые средства знаменитой операционной системы, предпочитаемой американскими университетами, казалось бы, предоставляли соответствующий базовый механизм, но, естественно, эта почтовая система показывала имя отправителя при доставке сообщения получателям. У меня был доступ к исходному коду - огромной программе на Си, и я решил, наверное, по глупости, взять этот код, убрать в нем все ссылки на имя отправителя в доставляемых сообщениях и перекомпилировать.
С помощью своего ассистента я взялся за работу, казавшуюся достаточно очевидной, хотя ей и не учат в курсах по разработке ПО, - систематическую разборку программы. Будучи уверенными в себе, мы быстро нашли первое место, в котором программа обращалась к имени отправителя, и удалили соответствующий код. После чего наивно решили, что работа сделана, и перекомпилировали код. Но когда мы послали тестовое сообщение, то обнаружили, что имя отправителя все еще сохранилось! После чего начался долгий и сюрреалистический процесс: снова и снова, веря, что мы, наконец, обнаружили последнее обращение к имени отправителя, мы удаляли его, перекомпилировали программу и посылали тестовое сообщение, чтобы в очередной раз исправно обнаружить имя отправителя на обычном месте. Как знаменитая стоглавая гидра, почтовая программа отращивала новую голову всякий раз, когда мы считали, что отрубили ей последнюю.
Наконец, повторив в нашу эру древний подвиг Геракла, мы полностью уничтожили этого зверя, удалив более двадцати участков кода, каждый из которых, так или иначе, задавал информацию об отправителе.
В предыдущих разделах нам удалось сделать первые шаги по дороге к АТД. Их достаточно для понимания того, что программа, написанная в соответствии с самыми элементарными представлениями об абстракции данных, должна была бы рассматривать MAIL_MESSAGE (ПОЧТОВОЕ_СООБЩЕНИЕ) как точно определенное абстрактное понятие. Одной из операций сообщения мог быть запрос, называемый, например, sender (отправитель), возвращающий информацию об отправителе сообщения. Любой элемент почтовой программы, которому была бы нужна эта информация, получал бы ее только через этот запрос sender. Если бы почтовая программа была разработана в соответствии с этим, кажущимся очевидным, принципом, то для моего небольшого упражнения достаточно было бы изменить только код запроса sender. Более того, весьма вероятно, что в этом случае программа предоставляла бы также и операцию set_sender (установить_отправителя), которая позволила бы выполнить требуемую работу еще проще.
Отметим, что рассматриваемая почтовая программа использовалась весьма успешно. Но она является типичным представителем нынешнего стандарта в индустрии ПО. До тех пор, пока мы не выйдем далеко за пределы этого стандарта фраза "проектирование программного обеспечения" останется примером принятия желаемого за действительное.
Формализация спецификаций
Представленный выше беглый набросок абстракции данных слишком неформален, чтобы его можно было постоянно использовать. Вернемся к нашему главному примеру. Стек, как мы это поняли, должен определяться в терминах применимых к нему операций, но тогда нам нужно определить эти операции!
Приведенные содержательные описания явно недостаточны - put вталкивает элемент на "вершину" стека, remove выталкивает элемент, находящийся на вершине. Нам нужно точно знать, как клиенты могут использовать эти операции и что они для этого должны делать.
Спецификация АТД предоставит эту информацию. Она состоит из четырех разделов, разъясняемых в следующих разделах:
[x]. ТИПЫ
[x]. ФУНКЦИИ
[x]. АКСИОМЫ
[x]. ПРЕДУСЛОВИЯ
Для спецификации АТД в этих разделах будут использоваться простая математическая нотация.
Эту нотацию - математический формализм - не надо путать с программной нотацией в остальной части книги, даже если для согласования она использует тот же стиль синтаксиса. У нее нет специального имени, и она не является нотацией языка программирования. Она могла бы послужить отправной точкой для формального языка спецификаций, но мы удовлетворимся использованием не требующих объяснения соглашений для однозначной спецификации АТД.Специфицирование типов
В разделе ТИПЫ указываются специфицируемые типы. В общем случае, может оказаться удобным определять одновременно несколько АТД, хотя в нашем примере имеется лишь один тип STACK(СТЕК). Между прочим, что такое тип? Ответ на этот вопрос объединит все положения, развиваемые далее в этой лекции: тип - это совокупность объектов, характеризуемая функциями, аксиомами и предусловиями. Не будет большой ошибкой рассматривать пока тип как множество объектов в математическом смысле слова "множество" - тип STACK как множество всех возможных стеков, тип INTEGER как множество всех целых чисел и т.д.
Однако при этом не должно быть никакой путаницы: АТД, такой как STACK, - это не объект (один конкретный стек), а совокупность объектов (множество всех стеков). Напомним, в чем состоит наша главная цель: найти подходящую основу для модулей наших программных систем. Очевидно, не имеет смысла делать основой для модуля один конкретный объект - один стек, один самолет, один счет в банке. ОО-проектирование даст нам возможность строить модули, отражающие свойства всех стеков, всех самолетов, всех банковских счетов, или, по крайней мере, значительной их части.
Объект, принадлежащий множеству объектов, описываемых спецификацией АТД, называется экземпляром этого АТД. Например, конкретный стек, обладающий свойствами абстрактного типа данных STACK, будет экземпляром АТД STACK. Понятие экземпляра проходит через все ОО-проектирование и программирование, и будет играть важную роль в объяснении поведения программ во время исполнения.
В разделе ТИПЫ просто перечисляются типы, вводимые в данной спецификации. Здесь:
Типы
[x]. STACK[G]
Таким образом, наша спецификация относится к одному абстрактному типу данных - STACK, задающему стеки объектов произвольного типа G.
Универсализация (Genericity)
В описании STACK[G] именем G обозначен произвольный, не определяемый тип. G называется формальным родовым параметром для типов элементов АТД STACK, а сам STACK называется родовым или универсальным АТД. Механизм, допускающий такие параметризованные спецификации, известен как универсализация, мы уже сталкивались с аналогичным понятием в обзоре конструкций пакетов.