Все права на текст принадлежат автору: Гради Буч.
Это короткий фрагмент для ознакомления с книгой.
Объектно-ориентированный анализ и проектирование с примерами приложений на С++Гради Буч

Объектно-ориентированный анализ и проектирование с примерами приложений на С++



ВТОРОЕ ИЗДАНИЕ


Rational Санта-Клара, Калифорния

перевод с английского под редакцией И. Романовского и Ф. Андреева

Книга Гради Буча, признанного эксперта в области объекто - ориентированной методологии разработки программного обеспечения, содержит классическое изложение вопросов анализа и проектирования сложных систем. В первой части книги автор исследует суть фундаментальных понятий ООП (таких как `класс`, `объект`, `наследование`), анализирует концепции, лежащие в основе объектно - ориентированных языков и методик разработки. Вторая часть содержит подробное описание обозначений (известных как `нотация Буча`), давноуже ставших родными для тысяч разработчиков во всем мире. Здесь же автор делится своим богатым опытом организации процесса разработки программ, дает рекомендации по подбору команды и планированию промежуточных релизов. В третьей части изложенные ранее методы применяются для анализа и проектирования нескольких приложений. На глазах у читателя создается каркас соответствующих систем, принимаются принципиальные проектные решения. Книга будет полезна аналитикам и разработчикам программного обеспечения, преподавателям и студентам высших учебных заведений. По сравнению с первым изданием книга несколько дополнена (что отразилось и в названии), все примеры приведены на языке С++.

Об авторе

Гради Буч (Grady Booch), главный исследователь корпорации Rational Software, признан всем международным сообществом разработчиков программного обеспечения благодаря его основополагающим работам в области объектно-ориентированных методов и приложений. Он - постоянный автор в таких журналах, как "Object Magazine" и "C++ Report" и автор многих бестселлеров, посвященных объектно-ориентированному проектированию и разработке программ. Гради Буч редактирует и участвует в написании серии "Разработка объектно-ориентированного программного обеспечения" ("Object-oriented Software Engineering Series"), издаваемой Addison-Wesley Longman.  


Человечество, по милости Божией, жаждет душевного покоя, эстетических достижений, безопасности семьи, справедливости и свободы, не удовлетворяясь повышением производительности труда. Но именно повышение производительности труда позволяет делиться избыточным, а не драться за недостающее; оно освобождает время для духовного, эстетического и семейного. Оно позволяет обществу направить часть своих средств на институты религии, правосудия и сохранения свобод.

Арлан Миллс (Harlan Mills) DPMA и человеческая производительность (DPMA and Human Productivity)

Предисловие

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

Что изменилось по сравнению с первым изданием

Со времени выхода в свет первого издания книги "Объектно-ориентированное проектирование с примерами применения" ("Object-Oriented Design with Applications") объектно-ориентированная технология стала одной из основных при разработке программного обеспечения промышленного масштаба. Мы видим, что во всем мире объектная парадигма применяется в таких различных областях, как управление банковскими транзакциями, автоматизация кегельбанов, управление коммунальным хозяйством и исследование генов человека. Во многих случаях новые поколения операционных систем, систем управления базами данных, телефонных служб, систем авионики и мультимедиа-программ пишутся в объектно-ориентированном стиле. В большинстве таких проектов предпочли использовать объектно-ориентированную технологию просто потому, что не было другой возможности создать достаточно надежную и жизнеспособную систему.

За последние годы в сотнях проектов применяли нотацию и процесс разработки, предложенные в нашей книге [Включая мои собственные проекты. Я все же разработчик, а не методолог. Первый вопрос, который нужно задавать каждому методологу: "Используете ли вы ваши методы при разработке собственных программ?"]. В процессе собственной разработки проектов и с учетом опыта многих других, кто пожертвовал своим временем, чтобы поделиться с нами, мы нашли много способов усовершенствовать наш метод. Усовершенствование достигается за счет лучшего изложения процесса проектирования, введения семантики, которая ранее не была отражена в нашей нотации, и упрощения этой нотации там, где возможно.

За истекшее время появились многие другие методы, изложенные в работах Джекобсона (Jacobson), Румбаха (Rumbaugh), Гоада и Иордана (Goad and Yourdon), Константайна (Constantine), Шлера и Меллора (Shiaer and Mellor), Мартина и Одел-ла (Martin and Odell), Вассермана (Wasserman), Голдберга и Рубина (Goldberg and Rubin), Эмбли (Embley), Вирфс-Брока (Wirfs-Brock), Голдстейна и Алгера (Goldstein and Alger), Хендерсон-Селлерса (Henderson-Sellers), Файесмита (Firesmith) и др. Особенно интересна работа Румбаха, который отмечает, что в наших подходах больше сходства чем различий. Мы провели анализ многих из этих методов, разговаривали с разработчиками и менеджерами, которые их использовали, и, когда это было возможно, пытались сами их применять. Так как мы больше заинтересованы в реальной помощи по разработке проектов в объектно-ориентированной технологии, чем в догматическом следовании (будь то по эмоциональным или историческим причинам) нашим идеям, мы пытались включить все лучшее, что нашли в новых методах, в нашу собственную работу. Мы с благодарностью отмечаем фундаментальный и уникальный вклад каждого из этих лиц в данную область.

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

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

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

Во-вторых, значительно расширены главы 6 и 7, в которых рассматривается практика объектно-ориентированного анализа и проектирования. Мы даже сменили в этом издании заглавие книги, отразив тот факт, что наш метод объединяет анализ и проектирование.

В-третьих, мы решили приводить примеры всех программных текстов в основной части книги на одном языке, а именно на C++. Этот язык быстро становится фактическим стандартом для многих областей, кроме того, большинство профессиональных разработчиков, "сочиняющих" на других языках, могут "читать" на C++. Это не значит, что мы считаем другие языки - такие, как Smalltalk, CLOS, Ada или Eiffel - менее важными. Главная цель этой книги - анализ и проектирование, и так как нам нужны конкретные примеры, мы решили писать их на достаточно общем языке программирования. Где возможно, мы описываем особенности семантики других языков и их влияние на наш метод.

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

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

Цели

Эта книга призвана служить практическим руководством по созданию объектно-ориентированных систем. Особое внимание мы уделяем следующим целям:

• обеспечить отчетливое понимание основных концепций объектной модели;

• помочь освоить систему обозначений и процесс объектно-ориентированного анализа и проектирования;

• научить читателя практическому применению объектно-ориентированного подхода в различных предметных областях.

Изложенные здесь понятия имеют серьезное теоретическое обоснование, но эта книга прежде всего призвана удовлетворить практические потребности и интересы сообщества разработчиков программных продуктов.

Аудитория

Книга предназначена и для профессионалов, и для студентов:

• Разработчику-практику мы покажем, как эффективно применять объектно-ориентированную технологию для решения реальных задач.

• Если вы выступаете в роли аналитика или архитектора системы, мы поможем вам пройти путь от постановки задачи до реализации, с использованием объектно-ориентированного анализа и проектирования. Мы разовьем вашу способность отличать "хорошую" объектно-ориентированную архитектуру от "плохой" и находить правильное решение в сложном реальном мире. Возможно самое важное, что мы предлагаем - новые подходы к рассмотрению сложных систем.

• Менеджеру программного проекта мы подскажем, как распределить ресурсы в команде разработчиков и снизить издержки, связанные с написанием любой сложной программной системы.

• Создателю инструментальных программных средств и их пользователю мы предложим подробное изложение системы обозначений и процесса объектно-ориентированной разработки - основы CASE (computer-aided software engineering, разработка программ с помощью компьютера).

• Студенту книга будет полезна, как основа, которая поможет приобрести начальные знания и навыки в искусстве создания сложных систем.

Книга может быть использована при чтении курсов для студентов и аспирантов, а также при проведении профессиональных семинаров и самостоятельном изучении. Так как она посвящена в основном методу построения программ, книга идеально подойдет для курсов проектирования программных продуктов и даст материал для дополнительных занятий по курсам объектно-ориентированных языков.

Структура

Книга делится на три большие части - "Концепции", "Метод" и "Примеры приложений" - с добавлением значительного дополнительного материала.

Концепции

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

Метод

Вторая часть описывает метод построения сложных систем, основанный на объектной модели. Сначала мы вводим систему графических обозначений объектно-ориентированного анализа и проектирования, а затем рассматриваем процесс разработки. Мы затрагиваем и практические вопросы, в частности роль этого процесса в жизненном цикле программного продукта и его значение для управления проектами.

Примеры приложений

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

Дополнительный материал

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

Помимо этой книги, можно порекомендовать "Сборник задач", содержащий упражнения, вопросы и проекты, которые должны оказаться полезными для семинарских занятий. "Сборник задач" ("Instructor's Guide with Exercises", ISBN 0-8053-5341-0) написан Мэри Бет Россон (Mary Beth Rosson) из лаборатории Томаса Дж. Ватсона (Thomas J. Watson) корпорации IBM. Преподаватели, желающие получить эту книгу, могут обращаться за бесплатным экземпляром непосредственно в издательство Addison-Wesley Longman (aw.cse@aw.com) или к местному представителю этого издательства. Вопросы и предложения для сборника задач можно направлять по адресу: rosson@watson.ibm.com.

Приобрести инструментальные средства и пройти обучение методу Буча (Booch) можно в разных местах. За дополнительной информацией обращайтесь в компанию Rational: booch-card@rational.com. Кроме того, Addison-Wesley Longman может предоставить учебным заведениям программные средства, поддерживающие нашу нотацию.

Как пользоваться этой книгой?

Книгу можно читать от корки до корки, но можно и по-другому. Если вы нуждаетесь в глубоком понимании объектной концепции и принципов объектно-ориентированного проектирования, начните с главы 1 и следуйте далее по порядку. Если вам интересна в основном система обозначений и процесс объектно-ориентированного анализа и проектирования, начните с глав 5 и 6; менеджерам проектов, использующим этот метод, будет особенно интересна глава 7. Если вы интересуетесь практическим приложением объектно-ориентированной технологии к конкретной области, обратитесь к главам 8-12.

Благодарности

Книга посвящается моей жене в благодарность за ее любовь и поддержку.

На протяжении всей работы над первым и вторым изданиями много людей формировали мои взгляды на объектно-ориентированную разработку. Среди них были: Сэм Адаме (Sam Adams), Майк Акроид (Mike Akroid), Гленн Андерт (Glenn Andert), Сид Байлин (Sid Bailin), Кент Бек (Kent Beck), Даниел Бобров (Daniel Bobrow), Дик Больц (Dick Bolz), Дэйв Балман (Dave Bulman), Дэйв Бернстейн (Dave Bernstein), Кэйван Кэран (Kayvan Carun), Дэйв Коллинз (Dave Collins), Стив Кук (Steve Cook), Дамиан Конвэй (Damian Conway), Джим Коплиен (Jim Coplien), Брэд Кокс (Brad Сох), Ворд Канингэм (Ward Cunningham), Том ДеМар-ко (Torn DeMarco), МайкДелвин (Mike Delvin), Ричард Габриел (Richard Gabriel), Вильям Ценемерас (William Cenemeras), Адель Голдберг (Adele Goldberg), Ян Грэ-хем (lan Graham), Тони Хоар (Топу Ноаге), Джон Хопкинс (Jon Hopkins), Майкл Джэксон (Michael Jackson), Ральф Джонсон (Ralph Johnson), Джеймс Кемпф (James Kempf). Норм Керт (Norm Kerth), Иордан Крейндлер (Jordan Kreindler), Дуг Ли ( Doug Lea), Фил Леви (Phil Levy), Барбара Лисков ( Barbara Liskov), Клифф Лонгмэн (Cliff Longman), Джеймс МакФарлэйн (James MacFarlane), Масауд Милани (Masoud Milani), Арлан Миллс (Harlan Mills), Роберт Мюррей (Robert Murray), Стив Нейс (Steve Neis), Джин Уйе (Gene Ouye), Дэйв Парнас (Dave Parnas), Билл Риддел (Bill Riddel), Мэри Бет Россон (Mary Beth Rosson), Кенни Рубин (Кеппу Rubin), Джим Румбах (Jim Rumbaugh), Курт Шмукер (Kurt Schmucker), Эд Сейде-витц (Ed Seidewitz), Дэн Шифман (Dan Shiftman), Дэйв Стивенсон (Dave Stevenson), Бьерн Страуструп (Bjarne Stroustrup), Дэйв Томсон (Dave Thomson), Майк Вило (Mike Vilot), Тони Вассерман (Tony Wasserman), Питер Вегнер (Peter Wegner), Айсеал Байт (Iseult White), Джон Вильяме (John Williams), Ллойд Вильяме (Lloyd Williams), Марио Волчко (Mario Wolczko), Никлаус Вирт (Niklaus Wirth) и Эд Иордан (Ed Yourdon).

Практические главы этой книги формировались по мере моего участия в разработке сложных программных систем по всему миру для таких компаний как: Apple, Alcatel, Andersen Consulting, AT&T, Autotrol, Bell Northern Research, Boeing, Borland, Computer Sciences Corporation, Contel, Ericsson, Ferranti, General Electric, GTE, Holland Signaal, Hughes Aircraft Company, IBM, Lockheed, Martin Marietta, Motorola, NTT, Philips, Rockwell International, Shell Oil, Symantec, Taligent и TRW. Я общался с сотнями профессиональных программистов и менеджеров и благодарю их всех за то, что они помогли сделать эту книгу отвечающей проблемам реальной жизни.

Особая благодарность - компании Rational за поддержку моего труда. Спасибо также моему редактору Дэну Йоранстаду (Dan Joraanstad) за его постоянную поддержку и Тони Холлу (Tony Hall), рисунки которого внесли жизнь в то, что без них осталось бы еще одной скучной технической книгой. Наконец, спасибо трем моим кошкам, Кэми (Сату), Энни (Annie) и Тени (Shadow), составлявшим мне компанию в долгие часы ночной работы.


ЧАСТЬ ПЕРВАЯ Концепции

Сэр Исаак Ньютон по секрету признавался друзьям, что он знает, как гравитация ведет себя, но не знает, почему.

Лили Томлин (Lily Tomlin) В поисках признаков разумной жизни во Вселенной (The Search for Signs of Intelligent Life in the Universe)

Глава 1 Сложность

Врач, строитель и программистка спорили о том, чья профессия древнее. Врач заметил: "В Библии сказано, что Бог сотворил Еву из ребра Адама. Такая операция может быть проведена только хирургом, поэтому я по праву могу утверждать, что моя профессия самая древняя в мире". Тут вмешался строитель и сказал: "Но еще раньше в Книге Бытия сказано, что Бог сотворил из хаоса небо и землю. Это было первое и, несомненно, наиболее выдающееся строительство. Поэтому, дорогой доктор, вы не правы. Моя профессия самая древняя в мире". Программистка при этих словах откинулась в кресле и с улыбкой произнесла: "А кто же по-вашему сотворил хаос?"

1.1. Сложность, присущая программному обеспечению

Простые и сложные программные системы

Звезда в преддверии коллапса; ребенок, который учится читать; клетки крови, атакующие вирус, - это только некоторые из потрясающе сложных объектов физического мира. Компьютерные программы тоже бывают сложными, однако их сложность совершенно другого рода. Брукс пишет: "Эйнштейн утверждал, что должны существовать простые объяснения природных процессов, так как Бог не действует из каприза или по произволу. У программиста нет такого утешения: сложность, с которой он должен справиться, лежит в самой природе системы" [1].

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

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

Существенная черта промышленной программы - уровень сложности: один разработчик практически не в состоянии охватить все аспекты такой системы. Грубо говоря, сложность промышленных программ превышает возможности человеческого интеллекта. Увы, но сложность, о которой мы говорим, по-видимому, присуща всем большим программных системам. Говоря "присуща", мы имеем в виду, что эта сложность здесь неизбежна: с ней можно справиться, но избавиться от нее нельзя.

Конечно, среди нас всегда есть гении, которые в одиночку могут выполнить работу группы обычных людей-разработчиков и добиться в своей области успеха, сравнимого с достижениями Франка Ллойда Райта или Леонардо да Винчи. Такие люди нам нужны как архитекторы, которые изобретают новые идиомы, механизмы и основные идеи, используемые затем при разработке других систем. Однако, как замечает Петерс: "В мире очень мало гениев, и не надо думать, будто в среде программистов их доля выше средней" [2]. Несмотря на то, что все мы чуточку гениальны, в промышленном программировании нельзя постоянно полагаться на божественное вдохновение, которое обязательно поможет нам. Поэтому мы должны рассмотреть более надежные способы конструирования сложных систем. Для лучшего понимания того, чем мы собираемся управлять, сначала ответим на вопрос: почему сложность присуща всем большим программным системам?

Почему программному обеспечению присуща сложность?

Как говорит Брукс, "сложность программного обеспечения - отнюдь не случайное его свойство" [3]. Сложность вызывается четырьмя основными причинами:

• сложностью реальной предметной области, из которой исходит заказ на разработку;

• трудностью управления процессом разработки;

• необходимостью обеспечить достаточную гибкость программы;

• неудовлетворительными способами описания поведения больших дискретных систем.

Сложность реального мира. Проблемы, которые мы пытаемся решить с помощью программного обеспечения, часто неизбежно содержат сложные элементы, а к соответствующим программам предъявляется множество различных, порой взаимоисключающих требований. Рассмотрим необходимые характеристики электронной системы многомоторного самолета, сотовой телефонной коммутаторной системы и робота. Достаточно трудно понять, даже в общих чертах, как работает каждая такая система. Теперь прибавьте к этому дополнительные требования (часто не формулируемые явно), такие как удобство, производительность, стоимость, выживаемость и надежность! Сложность задачи и порождает ту сложность программного продукта, о которой пишет Брукс.

Эта внешняя сложность обычно возникает из-за "нестыковки" между пользователями системы и ее разработчиками: пользователи с трудом могут объяснить в форме, понятной разработчикам, что на самом деле нужно сделать. Бывают случаи, когда пользователь лишь смутно представляет, что ему нужно от будущей программной системы. Это в основном происходит не из-за ошибок с той или иной стороны; просто каждая из групп специализируется в своей области, и ей недостает знаний партнера. У пользователей и разработчиков разные взгляды на сущность проблемы, и они делают различные выводы о возможных путях ее решения. На самом деле, даже если пользователь точно знает, что ему нужно, мы с трудом можем однозначно зафиксировать все его требования. Обычно они отражены на многих страницах текста, "разбавленных" немногими рисунками. Такие документы трудно поддаются пониманию, они открыты для различных интерпретаций и часто содержат элементы, относящиеся скорее к дизайну, чем к необходимым требованиям разработки.

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

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

• под сопровождением понимается устранение ошибок;

• под эволюцией - внесение изменений в систему в ответ на изменившиеся требования к ней;

• под сохранением - использование всех возможных и невозможных способов для поддержания жизни в дряхлой и распадающейся на части системе.

К сожалению, опыт показывает, что существенный процент затрат на разработку программных систем тратится именно на сохранение.

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

Задача разработчиков программной системы - создать иллюзию простоты.

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

Проблема описания поведения больших дискретных систем. Когда мы кидаем вверх мяч, мы можем достоверно предсказать его траекторию, потому что знаем, что в нормальных условиях здесь действуют известные физические законы. Мы бы очень удивились, если бы, кинув мяч с чуть большей скоростью, увидели, что он на середине пути неожиданно остановился и резко изменил направление движения [Даже простые непрерывные системы могут иметь сложное поведение ввиду наличия хаоса. Хаос привносит случайность, исключающую точное предсказание будущего состояния системы. Например, зная начальное положение двух капель воды в потоке, мы не можем точно предсказать, на каком расстоянии друг от друга они окажутся по прошествии некоторого времени. Хаос проявляется в таких различных системах, как атмосферные процессы, химические реакции, биологические системы и даже компьютерные сети. К счастью, скрытый порядок, по-видимому, есть во всех хаотических системах, в виде так называемых аттракторов]. В недостаточно отлаженной программе моделирования полета мяча такая ситуация легко может возникнуть.

Внутри большой прикладной программы могут существовать сотни и даже тысячи переменных и несколько потоков управления. Полный набор этих переменных, их текущих значений, текущего адреса и стека вызова для каждого процесса описывает состояние прикладной программы в каждый момент времени. Так как исполнение нашей программы осуществляется на цифровом компьютере, мы имеем систему с дискретными состояниями. Аналоговые системы, такие, как движение брошенного мяча, напротив, являются непрерывными. Д. Парнас [4] пишет: "когда мы говорим, что система описывается непрерывной функцией, мы имеем ввиду, что в ней нет скрытых сюрпризов. Небольшие изменения входных параметров всегда вызовут небольшие изменения выходных". С другой стороны, дискретные системы по самой своей природе имеют конечное число возможных состояний, хотя в больших системах это число в соответствии с правилами комбинаторики очень велико. Мы стараемся проектировать системы, разделяя их на части так, чтобы одна часть минимально воздействовало на другую. Однако переходы между дискретными состояниями не могут моделироваться непрерывными функциями. Каждое событие, внешнее по отношению к программной системе, может перевести ее в новое состояние, и, более того, переход из одного состояния в другое не всегда детерминирован. При неблагоприятных условиях внешнее событие может нарушить текущее состояние системы из-за того, что ее создатели не смогли предусмотреть все возможные варианты. Представим себе пассажирский самолет, в котором система управления полетом и система электроснабжения объединены. Было бы очень неприятно, если бы от включения пассажиром, сидящим на месте 38J, индивидуального освещения самолет немедленно вошел бы в глубокое пике. В непрерывных системах такое поведение было бы невозможным, но в дискретных системах любое внешнее событие может повлиять на любую часть внутреннего состояния системы. Это, очевидно, и является главной причиной обязательного тестирования наших систем; но дело в том, что за исключением самых тривиальных случаев, всеобъемлющее тестирование таких программ провести невозможно. И пока у нас нет ни математических инструментов, ни интеллектуальных возможностей для полного моделирования поведения больших дискретных систем, мы должны удовлетвориться разумным уровнем уверенности в их правильности.

Последствия неограниченной сложности

"Чем сложнее система, тем легче ее полностью развалить" [5]. Строитель едва ли согласится расширить фундамент уже построенного 100-этажного здания. Это не просто дорого: делать такие вещи значит напрашиваться на неприятности. Но что удивительно, пользователи программных систем, не задумываясь, ставят подобные задачи перед разработчиками. Это, утверждают они, всего лишь технический вопрос для программистов.

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

Как мы можем изменить положение дел? Так как проблема возникает в результате сложности структуры программных продуктов, мы предлагаем сначала рассмотреть способы работы со сложными структурами в других областях. В самом деле, можно привести множество примеров успешно функционирующих сложных систем. Некоторые из них созданы человеком, например: космический челнок Space Shuttle, туннель под Ла-Маншем, большие фирмы типа Microsoft или General Electric. В природе существуют еще более сложные системы, например система кровообращения у человека или растение.

1.2. Структура сложных систем

Примеры сложных систем

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

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

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

Структура растений и животных. Ботаник пытается понять сходство и различия растений, изучая их морфологию, то есть форму и структуру. Растения - это сложные многоклеточные организмы. В результате совместной деятельности различных органов растений происходят такие сложные типы поведения, как фотосинтез и всасывание влаги.

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

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

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

В компьютере транзисторы используются как в схеме ЦП, так и жесткого диска. Аналогично этому большое число "унифицированных элементов" имеется во всех частях растения. Так Создатель достигал экономии средств выражения. Например, клетки служат основными строительными блоками всех структур растения; корни, стебли и листья растения состоят из клеток. И хотя любой из этих исходных элементов действительно является клеткой, существует огромное количество разнообразных клеток. Есть клетки, содержащие и не содержащие хлоропласт, клетки с оболочкой, проницаемой и непроницаемой для воды, и даже живые и умершие клетки.

При изучении морфологии растения мы не выделяем в нем отдельные части, отвечающие за отдельные фазы единого процесса, например, фотосинтеза. Фактически не существует централизованных частей, которые непосредственно координируют деятельность более низких уровней. Вместо этого мы находим отдельные части, которые действуют как независимые посредники, каждый из которых ведет себя достаточно сложно и при этом согласованно с более высокими уровнями. Только благодаря совместным действиям большого числа посредников образуется более высокий уровень функционирования растения. Наука о сложности называет это возникающим поведением. Поведение целого сложнее, чем поведение суммы его составляющих [6].

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

Жизнь растений и животных поддерживает значительное число механизмов надклеточного уровня, то есть более высокого уровня абстракции. И растения, и животные используют сосудистую систему для транспортировки внутри организма питательных веществ. И у тех, и у других может существовать различие полов внутри одного вида.

Структура вещества. Исследования в таких разных областях, как астрономия и ядерная физика, дают множество других примеров невероятно сложных систем. В этих двух дисциплинах мы найдем примеры иерархических структур. Астрономы изучают галактики, которые объединены в скопления, а звезды, планеты и другие небесные тела образуют галактику. Ядерщики имеют дело со структурной иерархией физических тел совсем другого масштаба. Атомы состоят из электронов, протонов и нейтронов; электроны, по-видимому, являются элементарными частицами, но протоны, нейтроны и другие тяжелые частицы формируются из еще более мелких компонентов, называемых кварками.

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

Структура социальных институтов. Как последний пример сложных систем рассмотрим структуру общественных институтов. Люди объединяются в группы для решения задач, которые не могут быть решены индивидуально. Одни организации быстро распадаются, другие функционируют на протяжении нескольких поколений. Чем больше организация, тем отчетливее проявляется в ней иерархическая структура. Транснациональные корпорации состоят из компаний, которые в свою очередь состоят из отделений, содержащих различные филиалы. Последним принадлежат уже отдельные офисы и т.д. Границы между частями организации могут изменяться, и с течением времени может возникнуть новая, более стабильная иерархия.

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

Пять признаков сложной системы

Исходя из такого способа изучения, можно вывести пять общих признаков любой сложной системы. Основываясь на работе Саймона и Эндо, Куртуа предлагает следующее наблюдение [7]:

1. "Сложные системы часто являются иерархическими и состоят из взаимозависимых подсистем, которые в свою очередь также могут быть разделены на подсистемы, и т.д., вплоть до самого низкого уровням."

Саймон отмечает: "тот факт, что многие сложные системы имеют почти разложимую иерархическую структуру, является главным фактором, позволяющим нам понять, описать и даже "увидеть" такие системы и их части" [8]. В самом деле, скорее всего, мы можем понять лишь те системы, которые имеют иерархическую структуру.

Важно осознать, что архитектура сложных систем складывается и из компонентов, и из иерархических отношений этих компонентов. Речтин отмечает: "Все системы имеют подсистемы, и все системы являются частями более крупных систем... Особенности системы обусловлены отношениями между ее частями, а не частями как таковыми" [9].

Что же следует считать простейшими элементами системы? Опыт подсказывает нам следующий ответ:

2. Выбор, какие компоненты в данной системе считаются элементарными, относительно произволен и в большой степени оставляется на усмотрение исследователя.

Низший уровень для одного наблюдателя может оказаться достаточно высоким для другого.

Саймон называет иерархические системы разложимыми, если они могут быть разделены на четко идентифицируемые части, и почти разложимыми, если их составляющие не являются абсолютно независимыми. Это подводит нас к следующему общему свойству всех сложных систем:

3. "Внутрикомпонентная связь обычно сильнее, чем связь между компонентами. Это обстоятельство позволяет отделять "высокочастотные" взаимодействия внутри компонентов от "низкочастотной" динамики взаимодействия между компонентами" [10].

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

Как мы уже говорили, многие сложные системы организованы достаточно экономными средствами. Поэтому Саймон приводит следующий признак сложных систем:

4. "Иерархические системы обычно состоят из немногих типов подсистем, по-разному скомбинированных и организованных" [11].

Иными словам и, разные сложные системы содержат одинаковые структурные части. Эти части могут использовать общие более мелкие компоненты, такие как клетки, или более крупные структуры, типа сосудистых систем, имеющиеся и у растений, и у животных.

Выше мы отмечали, что сложные системы имеют тенденцию к развитию во времени. Саймон считает, что сложные системы будут развиваться из простых гораздо быстрее, если для них существуют устойчивые промежуточные формы [12]. Гэлл [13] выражается более эффектно:

5. "Любая работающая сложная система является результатом развития работавшей более простой системы... Сложная система, спроектированная "с нуля", никогда не заработает. Следует начинать с работающей простой системы".

В процессе развития системы объекты, первоначально рассматривавшиеся как сложные, становятся элементарными, и из них строятся более сложные системы. Более того, невозможно сразу правильно создать элементарные объекты: с ними надо сначала повозиться, чтобы больше узнать о реальном поведении системы, и затем уже совершенствовать их.

Организованная и неорганизованная сложность

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

Этот пример наводит на мысль, что мы обращались с термином иерархия в весьма приблизительном смысле. Наиболее интересные сложные системы содержат много разных иерархий. В самолете, например, можно выделить несколько систем: питания, управления полетом и т.д. Такое разбиение дает структурную иерархию типа "быть частью". Эту же систему можно разложить совершенно другим способом. Например, турбореактивный двигатель - особый тип реактивного двигателя, a "Pratt and Whitney TF30" - особый тип турбореактивного двигателя. С другой стороны, понятие "реактивный двигатель" обобщает свойства, присущие всем реактивным двигателям; "турбореактивный двигатель" - это просто особый тип реактивного двигателя со свойствами, которые отличают его, например, от прямоточного.

Эта вторая иерархия представляет собой иерархию типа "is-a". Исходя из нашего опыта, мы сочли необходимым рассмотреть систему с двух точек зрения, как иерархию первого и второго типа. По причинам, изложенным в главе 2, мы назовем эти иерархии соответственно структурой классов и структурой объектов [Сложные программные системы включают также и другие типы иерархии. Особое значение имеют их модульная структура, которая описывает отношения между физическими компонентами системы, и иерархия процессов, которая описывает отношения между динамическими компонентами].

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

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

Структуры классов и объектов системы вместе мы называем архитектурой системы.

Человеческие возможности и сложные системы. Если мы знаем, как должны быть спроектированы сложные программные системы, то почему при создании таких систем мы сталкиваемся с серьезными проблемами? Как показано в главе 2, идея о том, как бороться со сложностью программ (эту идею мы будем называть объектный подход) относительно нова. Существует, однако, еще одна, по-видимому, главная причина: физическая ограниченность возможностей человека при работе со сложными системами.  

Рис. 1-1. Каноническая форма сложной системы.

Когда мы начинаем анализировать сложную программную систему, в ней обнаруживается много составных частей, которые взаимодействуют друг с другом различными способами, причем ни сами части системы, ни способы их взаимодействия не обнаруживают никакого сходства. Это пример неорганизованной сложности. Когда мы начинаем организовывать систему в процессе ее проектирования, необходимо думать сразу о многом. Например, в системе управления движением самолетов приходится одновременно контролировать состояние многих летательных аппаратов, учитывая такие их параметры, как местоположение, скорость и курс. При анализе дискретных систем необходимо рассматривать большие, сложные и не всегда детерминированные пространства состояний. К сожалению, один человек не может следить за всем этим одновременно. Эксперименты психологов, например Миллера, показывают, что максимальное количество структурных единиц информации, за которыми человеческий мозг может одновременно следить, приблизительно равно семи плюс-минус два [14]. Вероятно, это связано с объемом краткосрочной памяти у человека. Саймон также отмечает, что дополнительным ограничивающим фактором является скорость обработки мозгом поступающей информации: на восприятие каждой новой единицы информации ему требуется около 5 секунд [15].

Таким образом, мы оказались перед серьезной дилеммой. Сложность программных систем возрастает, но способность нашего мозга справиться с этой сложностью ограничена. Как же нам выйти из создававшегося затруднительного положения?

1.3. Внесение порядка в хаос

Роль декомпозиции

Как отмечает Дейкстра, "Способ управления сложными системами был известен еще в древности - divide et impera (разделяй и властвуй)" [16]. При проектировании сложной программной системы необходимо разделять ее на все меньшие и меньшие подсистемы, каждую из которых можно совершенствовать независимо. В этом случае мы не превысим пропускной способности человеческого мозга: для понимания любого уровня системы нам необходимо одновременно держать в уме информацию лишь о немногих ее частях (отнюдь не о всех). В самом деле, как заметил Парнас, декомпозиция вызвана сложностью программирования системы, поскольку именно эта сложность вынуждает делить пространство состояний системы [17].

Алгоритмическая декомпозиция. Большинство из нас формально обучено структурному проектированию "сверху вниз", и мы воспринимаем декомпозицию как обычное разделение алгоритмов, где каждый модуль системы выполняет один из этапов общего процесса. На рис. 1-2 приведен в качестве примера один из продуктов структурного проектирования: структурная схема, которая показывает связи между различными функциональными элементами системы. Данная структурная схема иллюстрирует часть программной схемы, изменяющей содержание управляющего файла. Она была автоматически получена из диаграммы потока данных специальной экспертной системой, которой известны правила структурного проектирования [18].

Объектно-ориентированная декомпозиция. Предположим, что у этой задачи существует альтернативный способ декомпозиции. На рис. 1-3 мы разделили систему, выбрав в качестве критерия декомпозиции принадлежность ее элементов к различным абстракциям данной проблемной области. Прежде чем разделять задачу на шаги типа Get formatted update (Получить изменения в отформатированном виде) и Add check sum (Прибавить к контрольной сумме), мы должны определить такие объекты как Master File (Основной файл) и Check Sum (Контрольная сумма), которые заимствуются из словаря предметной области.

Хотя обе схемы решают одну и ту же задачу, но они делают это разными способами. Во второй декомпозиции мир представлен совокупностью автономных действующих лиц, которые взаимодействуют друг с другом, чтобы обеспечить поведение системы, соответствующее более высокому уровню. Get formatted update (Получить изменения в отформатированном виде) больше не присутствует в качестве независимого алгоритма; это действие существует теперь как операция над объектом File of Updates (Файл изменений). Эта операция создает другой объект - Update to Card (Изменения в карте). Таким образом, каждый объект обладает своим собственным поведением, и каждый из них моделирует некоторый объект реального мира. С этой точки зрения объект является вполне осязаемой вещью, которая демонстрирует вполне определенное поведение. Объекты что-то делают, и мы можем, послав им сообщение, попросить их выполнить то-то и то-то. Так как наша декомпозиция основана на объектах, а не на алгоритмах, мы называем ее объектно-ориентированной декомпозицией.  

Рис. 1-2. Алгоритмическая декомпозиция.

Декомпозиция: алгоритмическая или объектно-ориентированная? Какая декомпозиция сложной системы правильнее - по алгоритмам или по объектам? В этом вопросе есть подвох, и правильный ответ на него: важны оба аспекта. Разделение по алгоритмам концентрирует внимание на порядке происходящих событий, а разделение по объектам придает особое значение агентам, которые являются либо объектами, либо субъектами действия. Однако мы не можем сконструировать сложную систему одновременно двумя способами, тем более, что эти способы по сути ортогональны [Лэнгдон предполагает, что эта ортогональность изучалась с древних времен. Он пишет: "К. X. Ваддингтон отметил, что такая дуальность взглядов прослеживается до древних греков. Пассивный взгляд предлагался Демокритом, который утверждал, что мир состоит из атомов. Эта позиция Демокрита ставила в центр всего материю. Классическим представителем другой стороны - активного взгляда - был Гераклит, который выделял понятие процесса"[34]]. Мы должны начать разделение системы либо по алгоритмам, либо по объектам, а затем, используя полученную структуру, попытаться рассмотреть проблему с другой точки зрения.

Опыт показывает, что полезнее начинать с объектной декомпозиции. Такое начало поможет нам лучше справиться с приданием организованности сложности программных систем. Выше этот объектный подход помог нам при описании таких непохожих систем, как компьютеры, растения, галактики и общественные институты. Как будет видно в дальнейшем (в главах 2 и 7), объектная декомпозиция имеет несколько чрезвычайно важных преимуществ перед алгоритмической. Объектная декомпозиция уменьшает размер программных систем за счет повторного использования общих механизмов, что приводит к существенной экономии выразительных средств. Объектно-ориентированные системы более гибки и проще эволюционируют со временем, потому что их схемы базируется на устойчивых промежуточных формах. Действительно, объектная декомпозиция существенно снижает риск при создании сложной программной системы, так как она развивается из меньших систем, в которых мы уже уверены. Более того, объектная декомпозиция помогает нам разобраться в сложной программной системе, предлагая нам разумные решения относительно выбора подпространства большого пространства состояний.

Преимущества объектно-ориентированных систем демонстрируются в главах 8-12 примерами прикладных программ, относящихся к различным областям. Следующая врезка сопоставляет объектно-ориентированное проектирование с более традиционными подходами.  

Рис. 1-3. Объектно-ориентированная декомпозиция.

Роль абстракции

Выше мы ссылались на эксперименты Миллера, в которых было установлено, что обычно человек может одновременно воспринять лишь 7╠2 единицы информации. Это число, по-видимому, не зависит от содержания информации. Как замечает сам Миллер: "Размер нашей памяти накладывает жесткие ограничения на количество информации, которое мы можем воспринять, обработать и запомнить. Организуя поступление входной информации одновременно по нескольким различным каналам и в виде последовательности отдельных порций, мы можем прорвать... этот информационный затор" [35]. В современной терминологии это называют разбиением или выделением абстракций.  

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

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

В 60-70-е годы было разработано много методов, помогающих справиться с растущей сложностью программ. Наибольшее распространение получило структурное проектирование по методу сверху вниз. Метод был непосредственно основан на топологии традиционных языков высокого уровня типа FORTRAN или COBOL. В этих языках основной базовой единицей является подпрограмма, и программа в целом принимает форму дерева, в котором одни подпрограммы в процессе работы вызывают другие подпрограммы. Структурное проектирование использует именно такой подход: алгоритмическая декомпозиция применяется для разбиения большой задачи на более мелкие.

Тогда же стали появляться компьютеры еще больших, поистине гигантских возможностей. Значение структурного подхода осталось прежним, но как замечает Стейн, "оказалось, что структурный подход не работает, если объем программы превышает приблизительно 100000 строк" [19]. В последнее время появились десятки методов, в большинстве которых устранены очевидные недостатки структурного проектирования. Наиболее удачные методы были разработаны Петерсом [20], Йеном и Цаи [21], а также фирмой Teledyne-Brown Engineering [22]. Большинство этих методов представляют собой вариации на одни и те же темы. Саммервилль предлагает разделить их на три основные группы [23]:

• метод структурного проектирования сверху вниз;

• метод потоков данных;

• объектно-ориентированное проектирование.

Примеры структурного проектирования приведены в работах Иордана и Константина [24], Майерса [25] и Пейдж-Джонса [26]. Основы его изложены в работах Вирта [27, 28], Даля, Дейкстры и Хоара [29]; интересный вариант структурного подхода можно найти в работе Милса, Лингера и Хевнера [30]. В каждом из этих подходов присутствует алгоритмическая декомпозиция. Следует отметить, что большинство существующих программ написано, по-видимому, в соответствии с одним из этих методов. Тем не менее структурный подход не позволяет выделить абстракции и обеспечить ограничение доступа к данным; он также не предоставляет достаточных средств для организации параллелизма. Структурный метод не может обеспечить создание предельно сложных систем, и он, как правило, неэффективен в объектных и объектно-ориентированных языках программирования.

Метод потоков данных лучше всего описан в ранней работе Джексона [31, 32], а также Варниера и Орра [33]. В этом методе программная система рассматривается как преобразователь входных потоков в выходные. Метод потоков данных, как и структурный метод, с успехом применялся при решении ряда сложных задач, в частности, в системах информационного обеспечения, где существуют прямые связи между входными и выходными потоками системы и где не требуется уделять особого внимания быстродействию.

Объектно-ориентированное проектирование (object-oriented design, OOD) - это подход, основы которого изложены в данной книге. В основе OOD лежит представление о том, что программную систему необходимо проектировать как совокупность взаимодействующих друг с другом объектов, рассматривая каждый объект как экземпляр определенного класса, причем классы образуют иерархию. Объектно-ориентированный подход отражает топологию новейших языков высокого уровня, таких как Smalltalk, Object Pascal, C++, CLOS и Ada. 

Вулф так описывает этот процесс: "Люди развили чрезвычайно эффективную технологию преодоления сложности. Мы абстрагируемся от нее. Будучи не в состоянии полностью воссоздать сложный объект, мы просто игнорируем не слишком важные детали и, таким образом, имеем дело с обобщенной, идеализированной моделью объекта" [36]. Например, изучая процесс фотосинтеза у растений, мы концентрируем внимание на химических реакциях в определенных клетках листа и не обращаем внимание на остальные части - черенки, жилки и т.д. И хотя мы по-прежнему вынуждены охватывать одновременно значительное количество информации, но благодаря абстракции мы пользуемся единицами информации существенно большего семантического объема. Это особенно верно, когда мы рассматриваем мир с объектно-ориентированной точки зрения, поскольку объекты как абстракции реального мира представляют собой отдельные насыщенные связные информационные единицы.

В главе 2 понятие абстракции рассмотрено более детально.

Роль иерархии

Другим способом, расширяющим информационные единицы, является организация внутри системы иерархий классов и объектов. Объектная структура важна, так как она иллюстрирует схему взаимодействия объектов друг с другом, которое осуществляется с помощью механизмов взаимодействия. Структура классов не менее важна: она определяет общность структур и поведения внутри системы. Зачем, например, изучать фотосинтез каждой клетки отдельного листа растения, когда достаточно изучить одну такую клетку, поскольку мы ожидаем, что все остальные ведут себя подобным же образом. И хотя мы рассматриваем каждый объект определенного типа как отдельный, можно предположить, что его поведение будет похоже на поведение других объектов того же типа. Классифицируя объекты по группам родственных абстракций (например, типы клеток растений в противовес клеткам животных), мы четко разделяем общие и уникальные свойства разных объектов, что помогает нам затем справляться со свойственной им сложностью [37].

Определить иерархии в сложной программной системе не всегда легко, так как это требует разработки моделей многих объектов, поведение каждого из которых может отличаться чрезвычайной сложностью. Однако после их определения, структура сложной системы и, в свою очередь, наше понимание ее сразу во многом проясняются. В главе 3 детально рассматривается природа иерархий классов и объектов, а в главе 4 описываются приемы распознавания этих структур.

1.4. О проектировании сложных систем

Инженерное дело как наука и искусство

На практике любая инженерная дисциплина, будь то строительство, механика, химия, электроника или программирование, содержит в себе элементы и науки, и искусства. Петроски красноречиво утверждает: "Разработка новых структур предполагает и полет фантазии, и синтез опыта и знаний: все то, что необходимо художнику для реализации своего замысла на холсте или бумаге. После того, как этот замысел созрел в голове инженера-художника, он обязательно должен быть проанализирован с точки зрения применимости данного научного метода инженером-ученым со всей тщательностью, присущей настоящему ученому" [38]. Аналогично, Дейкстра отмечает, что "Программная постановка задачи является упражнением в применении абстракции и требует способностей как формального математика, так и компетентного инженера" [39].

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

Смысл проектирования

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

• "удовлетворяет заданным (возможно, неформальным) функциональным спецификациям;

• согласована с ограничениями, накладываемыми оборудованием;

• удовлетворяет явным и неявным требованиям по эксплуатационным качествам и ресурсопотреблению;

• удовлетворяет явным и неявным критериям дизайна продукта;

• удовлетворяет требованиям к самому процессу разработки, таким, например, как продолжительность и стоимость, а также привлечение дополнительных инструментальных средств" [40].

По предположению Страуструпа: "Цель проектирования - выявление ясной и относительно простой внутренней структуры, иногда называемой архитектурой... Проект есть окончательный продукт процесса проектирования" [41]. Проектирование подразумевает учет противоречивых требований. Его продуктами являются модели, позволяющие нам понять структуру будущей системы, сбалансировать требования и наметить схему реализации.

Важность построения модели. Моделирование широко распространено во всех инженерных дисциплинах, в значительной степени из-за того, что оно реализует принципы декомпозиции, абстракции и иерархии [42]. Каждая модель описывает определенную часть рассматриваемой системы, а мы в свою очередь строим новые модели на базе старых, в которых более или менее уверены. Модели позволяют нам контролировать наши неудачи. Мы оцениваем поведение каждой модели в обычных и необычных ситуациях, а затем проводим соответствующие доработки, если нас что-то не удовлетворяет.

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

Элементы программного проектирования. Ясно, что не существует такого универсального метода, "серебряной пули" [43], который бы провел инженера-программиста по пути от требований к сложной программной системе до их выполнения. Проектирование сложной программной системы отнюдь не сводится к слепому следованию некоему набору рецептов. Скорее это постепенный и итеративный процесс. И тем не менее использование методологии проектирования вносит в процесс разработки определенную организованность. Инженеры-программисты разработали десятки различных методов, которые мы можем классифицировать по трем категориям. Несмотря на различия, эти методы имеют что-то общее. Их, в частности, объединяет следующее:

• условные обозначения - язык для описания каждой модели;

• процесс - правила проектирования модели;

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

Хороший метод проектирования базируется на прочной теоретической основе и при этом дает программисту известную степень свободы самовыражения.

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

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

Так как построение моделей крайне важно при проектировании сложных систем, объектно-ориентированное проектирование предлагает богатый выбор моделей, которые представлены на рис. 1-4. Объектно-ориентированные модели проектирования отражают иерархию и классов, и объектов системы. Эти модели покрывают весь спектр важнейших конструкторских решений, которые необходимо рассматривать при разработке сложной системы, и таким образом вдохновляют нас на создание проектов, обладающих всеми пятью атрибутами хорошо организованных сложных систем.

В главе 5 подробно рассмотрен каждый из четырех типов моделей. В главе 6 описан процесс объектно-ориентированного проектирования, представляющий собой цепь последовательных шагов по созданию и развитию моделей. В главе 7 рассмотрена практика управления процессом объектно-ориентированного проектирования.

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

Рис. 1-4. Объектно-ориентированные модели.

Выводы

• Программам присуща сложность, которая нередко превосходит возможности человеческого разума.

• Задача разработчиков программных систем - создать у пользователя разрабатываемой системы иллюзию простоты.

• Сложные структуры часто принимают форму иерархий; полезны обе иерархии: и классов, и объектов.

• Сложные системы обычно создаются на основе устойчивых промежуточных форм.

• Познавательные способности человека ограничены; мы можем раздвинуть их рамки, используя декомпозицию, выделение абстракций и создание иерархий.

• Сложные системы можно исследовать, концентрируя основное внимание либо на объектах, либо на процессах; имеются веские основания использовать объектно-ориентированную декомпозицию, при которой мир рассматривается как упорядоченная совокупность объектов, которые в процессе взаимодействия друг с другом определяют поведение системы.

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

Дополнительная литература

Проблемы, связанные с развитием сложных программных систем, были отчетливо описаны в классических работах Брукса (Brooks) [Н 1975] и [Н 1987]. В работах Гласса (Glass) [Н 1982], Defense Science Board [Н 1987], и Joint Service Task Force [Н 1982] можно найти более свежую информацию о современной практике программирования. Эмпирические исследования природы и причин программистских неудач можно найти в работах ван Генучтена (van Genuchten) [Н 1991], Гвиндона (Guindon) и др. [Н 1987], Джонса (Jones) [H1992].

Работы Саймона (Simon) [A 1962,1982] - богатый источник сведений об архитектуре сложных систем. Куртуа (Courtois) [A 1985] применил эти идеи к области программного обеспечения. Плодотворная работа Александера (Alexander) [I 1979] предлагает свежий подход к архитектуре. Питер (Peter) [I 1986] и Петроски (Petroski) [11985] изучали сложность в контексте соответственно социальных и физических систем. Аллен и Стар (Alien and Starr) [A 1982] изучали иерархические системы в ряде предметных областей. Флуд и Кэрсон (Flood and Carson) [A 1988] предприняли формальное исследование сложности сквозь призму теории систем. Волдрап (Waldrop) [A 1992] описал возникающую науку о сложности и ее использование при изучении больших адаптивных систем, возникающего поведения и самоорганизации. Отчет Миллера (Miller) [A 1956] дает эмпирические свидетельства фундаментальных ограничивающих факторов человеческого сознания.

По проектированию программного обеспечения есть ряд замечательных ссылок. Росс, Гудинаф и Ирвайн (Ross, Goodenough, and Irvine) [Н 1980], а также Зелковитс (Zeikowitz) [Н 1978] - это две классические работы, суммирующие существенные элементы проектирования. Более широкий круг работ по этому предмету включает: Дженсен и Тонис (Jensen and Tonies) [Н 1979], Саммервиль (Sommerville) [Н 1985], Вик и Рамамурти (Vick and Ramamourthy) [Н 1984], Вегнер (Wegner) [Н 1980], Пресман (Pressman) [Н 1992], Оман и Льюис (Oman and Lewis) [A 1990], Берзинс и Луки (Berzins and Luqi) [Н 1991] и Hг и Йeн(NgandYen) [Н 1990]. Другие статьи, касающиеся проектирования программного обеспечения , можно найти в Йордон (Yourdon) [Н 1979] и Фриман и Вассерман( Freeman and Wasserman)[H 1993]. Две работы, Грэхема (Graham) [F 1991] и Берарда (Berard) [Н 1993], предлагают широкое истолкование объектно-ориентированного проектирования.

Глейк (Gleik) [I 1987] предложил легко читаемое введение в хаосоведение.


Глава 2 Объектная модель

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

Объектно-ориентированный анализ и проектирование принципиально отличаются от традиционных подходов структурного проектирования: здесь нужно по-другому представлять себе процесс декомпозиции, а архитектура получающегося программного продукта в значительной степени выходит за рамки представлений, традиционных для структурного программирования. Отличия обусловлены тем, что структурное проектирование основано на структурном программировании, тогда как в основе объектно-ориентированного проектирования лежит методология объектно-ориентированного программирования, К сожалению, для разных людей термин "объектно-ориентированное программирование" означает разное. Ренч правильно предсказал: "В 1980-х годах объектно-ориентированное программирование будет занимать такое же место, какое занимало структурное программирование в 1970-х. но всем будет нравиться. Каждая фирма будет рекламировать свой продукт как зданный по этой технологии. Все программисты будут писать в этом стиле, причем все по-разному. Все менеджеры будут рассуждать о нем. И никто не будет знать, что же это такое" [Wegner, P. [J 1981]] [1]. Данные предсказания продолжают сбываться и в 1990-х годах.

В этой главе мы выясним, чем является и чем не является объектно-ориентированная разработка программ, и в чем отличия этого подхода к проектированию от других с учетом семи перечисленных выше элементов объектной модели.

2.1. Эволюция объектной модели

Тенденции в проектировании

Поколения языков программирования. Оглядываясь на короткую, но колоритную историю развития программирования, нельзя не заметить две сменяющих друг друга тенденции:

• смещение акцентов от программирования отдельных деталей к программированию более крупных компонент;

• развитие и совершенствование языков программирования высокого уровня.

Большинство современных коммерческих программных систем больше и существенно сложнее, чем были их предшественники даже несколько лет тому назад. Этот рост сложности вызвал большое число прикладных исследований по методологии проектирования, особенно, по декомпозиции, абстрагированию и иерархиям. Создание более выразительных языков программирования пополнило достижения в этой области. Возникла тенденция перехода от языков, указывающих компьютеру, что делать (императивные языки), к языкам, описывающим ключевые абстракции проблемной области (декларативные языки).

Вегнер сгруппировал некоторые из наиболее известных языков высокого уровня в четыре поколения в зависимости от того, какие языковые конструкции впервые в них появились:

• Первое поколение (1954-1958)

 FORTRAN I   Математические формулы 

 ALGOL-58   Математические формулы 

 Flowmatic   Математические формулы 

 IPL V   Математические формулы 

 

• Второе поколение (1959-1961)

 FORTRAN II   Подпрограммы, раздельная компиляция 

 ALGOL-60   Блочная структура, типы данных 

 COBOL   Описание данных, работа с файлами 

 Lisp   Обработка списков, указатели, сборка мусора 

 

• Третье поколение(1962-1970)

 PL/I   FORTRAN+ALGOL+COBOL 

 ALGOL-68   Более строгий приемник ALGOL-60 

 Pascal   Более простой приемник ALGOL-60 

 Simula   Классы, абстрактные данные 

 

• Потерянное поколение (1970-1980)

Много языков созданных, но мало выживших [Последняя фраза, очевидно, следует евангельскому "...много званных, но мало избранных" (Матф. 22:14). - Примеч. ред.] [2].

В каждом следующем поколении менялись поддерживаемые языками механизмы абстракции. Языки первого поколения ориентировались на научно-инженерные применения, и словарь этой предметной области был почти исключительно математическим. Такие языки, как FORTRAN I, были созданы для упрощения программирования математических формул, чтобы освободить программиста от трудностей ассемблера и машинного кода. Первое поколение языков высокого уровня было шагом, приближающим программирование к предметной области и удаляющим от конкретной машины. Во втором поколении языков основной тенденцией стало развитие алгоритмических абстракций. В это время мощность компьютеров быстро росла, а компьютерная индустрия позволила расширить области их применения, особенно в бизнесе. Главной задачей стало инструктировать машину, что делать: сначала прочти эти анкеты сотрудников, затем отсортируй их и выведи результаты на печать. Это было еще одним шагом к предметной области и от конкретной машины. В конце 60-х годов с появлением транзисторов, а затем интегральных схем, стоимость компьютеров резко снизилась, а их производительность росла почти экспоненциально. Появилась возможность решать все более сложные задачи, но это требовало умения обрабатывать самые разнообразные типы данных. Такие языки как ALGOL-68 и затем Pascal стали поддерживать абстракцию данных. Программисты смогли описывать свои собственные типы данных. Это стало еще одним шагом к предметной области и от привязки к конкретной машине.

70-е годы знаменовались безумным всплеском активности: было создано около двух тысяч различных языков и их диалектов. Неадекватность более ранних языков написанию крупных программных систем стала очевидной, поэтому новые языки имели механизмы, устраняющие это ограничение. Лишь немногие из этих языков смогли выжить (попробуйте найти свежий учебник по языкам Fred, Chaos, Tranquil), однако многие их принципы нашли отражение в новых версиях более ранних языков. Таким образом, мы получили языки Smalltalk (новаторски переработанное наследие Simula), Ada (наследник ALGOL-68 и Pascal с элементами Simula, Alphard и CLU), CLOS (объединивший Lisp, LOOPS и Flavors), C++ (возникший от брака С и Simula) и Eiffel (произошел от Simula и Ada). Наибольший интерес для дальнейшего изложения представляет класс языков, называемых объектными и объектно-ориентированными, которые в наибольшей степени отвечают задаче объектно-ориентированной декомпозиции программного обеспечения.

Топология языков первого и начала второго поколения. Для пояснения сказанного рассмотрим структуры, характерные для каждого поколения. На рис. 2-1 показана топология, типичная для большинства языков первого поколения и первой стадии второго поколения. Говоря "топология", мы имеем в виду основные элементы языка программирования и их взаимодействие. Можно отметить, что для таких языков, как FORTRAN и COBOL, основным строительным блоком является подпрограмма (параграф в терминах COBOL). Программы, реализованные на таких языках, имеют относительно простую структуру, состоящую только из глобальных данных и подпрограмм. Стрелками на рисунке обозначено влияние подпрограмм на данные. В процессе разработки можно логически разделить разнотипные Данные, но механизмы языков практически не поддерживают такого разделения. Ошибка в какой-либо части программы может иметь далеко идущие последствия, так как область данных открыта всем подпрограммам. В больших системах трудно гарантировать целостность данных при внесении изменений в какую-либо часть системы. В процессе эксплуатации уже через короткое время возникает путаница из-за большого количества перекрестных связей между подпрограммами, запутанных схем управления, неясного смысла данных, что угрожает надежности системы и определенно снижает ясность программы.  

Рис. 2-1. Топология языков первого и начала второго поколения.

Топология языков позднего второго и раннего третьего поколения. Начиная с середины 60-х годов стали осознавать роль подпрограмм как важного промежуточного звена между решаемой задачей и компьютером [3]. Шоу отмечает: "Первая программная абстракция, названная процедурной абстракцией, прямо вытекает из этого прагматического взгляда на программные средства... Подпрограммы возникли до 1950 года, но тогда они не были оценены в качестве абстракции... Их рассматривали как средства, упрощающие работу... Но очень скоро стало ясно, что подпрограммы это абстрактные программные функции" [4].

Использование подпрограмм как механизма абстрагирования имело три существенных последствия. Во-первых, были разработаны языки, поддерживавшие разнообразные механизмы передачи параметров. Во-вторых, были заложены основания структурного программирования, что выразилось в языковой поддержке механизмов вложенности подпрограмм и в научном исследовании структур управления и областей видимости. В-третьих, возникли методы структурного проектирования, стимулирующие разработчиков создавать большие системы, используя подпрограммы как готовые строительные блоки. Архитектура языков программирования этого периода (рис. 2-2), как и следовало ожидать, представляет собой вариации на темы предыдущего поколения. В нее внесены кое-какие усовершенствования, в частности, усилено управление алгоритмическими абстракциями, но остается нерешенной проблема программирования "в большом" и проектирования данных.

Топология языков конца третьего поколения. Начиная с FORTRAN II и далее, для решения задач программирования "в большом" начал развиваться новый важный механизм структурирования. Разрастание программных проектов означало увеличение размеров и коллективов программистов, а, следовательно, необходимость независимой разработки отдельных частей проекта. Ответом на эту потребность стал отдельно компилируемый модуль, который сначала был просто более или менее случайным набором данных и подпрограмм (рис. 2-3). В такие модули собирали подпрограммы, которые, как казалось, скорее всего будут изменяться совместно, и мало кто рассматривал их как новую технику абстракции. В большинстве языков этого поколения, хотя и поддерживалось модульное программирование, но не вводилось никаких правил, обеспечивающих согласование интерфейсов модулей. Программист, сочиняющий подпрограмму в одном из модулей, мог, например, ожидать, что ее будут вызывать с тремя параметрами: действительным числом, массивом из десяти элементов и целым числом, обозначающим логическое значение. Но в каком-то другом модуле, вопреки предположениям автора, эта подпрограмма могла по ошибке вызываться с фактическими параметрами в виде: целого числа, массива из пяти элементов и отрицательного числа. Аналогично, один из модулей мог завести общую область данных и считать, что это его собственная область, а другой модуль мог нарушить это предположение, свободно манипулируя с этими данными. К сожалению, поскольку большинство языков предоставляло в лучшем случае рудиментарную поддержку абстрактных данных и типов, такие ошибки выявлялись только при выполнении программы.  

Рис. 2-2. Топология языков позднего второго и раннего третьего поколения.

Топология объектных и объектно-ориентированных языков. Значение абстрактных типов данных в разрешении проблемы сложности систем хорошо выразил Шанкар: "Абстрагирование, достигаемое посредством использования процедур, хорошо подходит для описания абстрактных действий, но не годится для описания абстрактных объектов. Это серьезный недостаток, так как во многих практических ситуациях сложность объектов, с которыми нужно работать, составляет основную часть сложности всей задачи" [5]. Осознание этого влечет два важных вывода. Во-первых, возникают методы проектирования на основе потоков данных, которые вносят упорядоченность в абстракцию данных в языках, ориентированных на алгоритмы. Во-вторых, появляется теория типов, которая воплощается в таких языках, как Pascal.

Естественным завершением реализации этих идей, начавшейся с языка Simula и развитой в последующих языках в 1970-1980-е годы, стало сравнительно недавнее появление таких языков, как Smalltalk, Object Pascal, C++, CLOS, Ada и Eiffel. По причинам, которые мы вскоре объясним, эти языки получили название объектных или объектно-ориентированных. На рис. 2-4 приведена топология таких языков применительно к задачам малой и средней степени сложности. Основным элементом конструкции в указанных языках служит модуль, составленный из логически связанных классов и объектов, а не подпрограмма, как в языках первого поколения.  

Рис. 2-3. Топология языков конца третьего поколения.

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

В настоящее время мы продвинулись много дальше программирования "в большом" и предстали перед программированием "в огромном". Для очень сложных систем классы, объекты и модули являются необходимыми, но не достаточными средствами абстракции. К счастью, объектный подход масштабируется и может быть применен на все более высоких уровнях. Кластеры абстракций в больших системах могут представляться в виде многослойной структуры. На каждом уровне можно выделить группы объектов, тесно взаимодействующих для решения задачи более высокого уровня абстракции. Внутри каждого кластера мы неизбежно найдем такое же множество взаимодействующих абстракций (рис. 2-5). Это соответствует подходу к сложным системам, изложенному в главе 1.

Основные положения объектной модели

Методы структурного проектирования помогают упростить процесс разработки сложных систем за счет использования алгоритмов как готовых строительных блоков. Аналогично, методы объектно-ориентированного проектирования созданы, чтобы помочь разработчикам применять мощные выразительные средства объектного и объектно-ориентированного программирования, использующего в качестве блоков классы и объекты.

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

Рис. 2-4. Топология малых и средних приложений в объектных и объектно-ориентированных языках.

Объектно-ориентированный анализ и проектирование отражают эволюционное, а не революционное развитие проектирования; новая методология не порывает с прежними методами, а строится с учетом предшествующего опыта. К сожалению, большинство программистов в настоящее время формально и неформально натренированы на применение только методов структурного проектирования. Разумеется, многие хорошие проектировщики создали и продолжают совершенствовать большое количество программных систем на основе этой методологии. Однако алгоритмическая декомпозиция помогает только до определенного предела, и обращение к объектно-ориентированной декомпозиции необходимо. Более того, при попытках использовать такие языки, как C++ или Ada, в качестве традиционных, алгоритмически ориентированных, мы не только теряем их внутренний потенциал - скорее всего результат будет даже хуже, чем при использовании обычных языков С и Pascal. Дать электродрель плотнику, который не слышал об электричестве, значит использовать ее в качестве молотка. Он согнет несколько гвоздей и разобьет себе пальцы, потому что электродрель мало пригодна для замены молотка.

OOP, OOD и ООА

Унаследовав от многих предшественников, объектный подход, к сожалению, перенял и запутанную терминологию. Программист в Smalltalk пользуется термином метод, в C++ - термином виртуальная функция, в CLOS - обобщенная функция.  

Рис. 2-5. Топология больших приложений в объектных и объектно-ориентированных языках.

В Object Pascal используется термин приведение типов, а в языке Ada то же самое называется преобразование типов. Чтобы уменьшить путаницу, следует определить, что является объектно-ориентированным, а что - нет. Определение наиболее употребительных терминов и понятий вы найдете в глоссарии в конце книги.

Термин объектно-ориентированный, по мнению Бхаскара, "затаскан до потери смысла, как "материнство", "яблочный пирог" и "структурное программирование"" [7]. Можно согласиться, что понятие объекта является центральным во всем, что относится к объектно-ориентированной методологии. В главе 1 мы определили объект как осязаемую сущность, которая четко проявляет свое поведение. Стефик и Бобров определяют объекты как "сущности, объединяющие процедуры и данные, так как они производят вычисления и сохраняют свое локальное состояние" [8]. Определение объекта как сущности в какой-то мере отвечает на вопрос, но все же главным в понятии объекта является объединение идей абстракции данных и алгоритмов. Джонс уточняет это понятие следующим образом: "В объектном подходе акцент переносится на конкретные характеристики физической или абстрактной системы, являющейся предметом программного моделирования... Объекты обладают целостностью, которая не должна - а, в действительности, не может - быть нарушена. Объект может только менять состояние, вести себя, управляться или становиться в определенное отношение к другим объектам. Иначе говоря, свойства, которые характеризуют объект и его поведение, остаются неизменными. Например, лифт характеризуется теми неизменными свойствами, что он может двигаться вверх и вниз, оставаясь в пределах шахты... Любая модель должна учитывать эти свойства лифта, так как они входят в его определение" [32].  

 Основные положения объектной модели

Йонесава и Токоро свидетельствуют: "термин "объект" появился практически независимо в различных областях, связанных с компьютерами, и почти одновременно в начале 70-х годов для обозначения того, что может иметь различные проявления, оставаясь целостным. Для того, чтобы уменьшить сложность программных систем, объектами назывались компоненты системы или фрагменты представляемых знании" [9]. По мнению Леви, объектно-ориентированный подход был связан со следующими событиями:

• "прогресс в области архитектуры ЭВМ;

• развитие языков программирования, таких как Simula, Smalltalk, CLU, Ada;

• развитие методологии программирования, включая принципы модульности и скрытия данных" [10].

К этому еще следует добавить три момента, оказавшие влияние на становление объектного подхода:

• развитие теории баз данных;

• исследования в области искусственного интеллекта;

• достижения философии и теории познания.

Понятие "объект" впервые было использовано более 20 лет назад при конструировании компьютеров с descriptor-based и capability-based архитектурами [11]. В этих работах делались попытки отойти от традиционной архитектуры фон Неймана и преодолеть барьер между высоким уровнем программной абстракции и низким уровнем ЭВМ [12]. По мнению сторонников этих подходов, тогда были созданы более качественные средства, обеспечивающие: лучшее выявление ошибок, большую эффективность реализации программ, сокращение набора инструкций, упрощение компиляции, снижение объема требуемой памяти. Ряд компьютеров имеет объектно-ориентированную архитектуру: Burroughs 5000, Plessey 250, Cambridge CAP [13], SWARD [14], Intel 432 [15], Caltech's СОМ [16], IBM System/38 [17], Rational R1000, BiiN 40 и 60.

С объектно-ориентированной архитектурой тесно связаны объектно-ориентированные операционные системы (ОС). Дейкстра, работая над мультипрограммной системой THE, впервые ввел понятие машины с уровнями состояния в качестве средства построения системы [18]. Среди первых объектно-ориентированных ОС следует отметить: Plessey/System 250 (для мультипроцессора Plessey 250), Hydra (для CMU C.mmp), CALTSS (для CDC 6400), CAP (для компьютера Cambridge CAP), UCLA Secure UNIX (для PDP 11/45 и 11/70), StarOS (для CMU Cm*), Medusa (также для CMU Cm*) и iMAX (для Intel 432) [19]. Следующее поколение операционных систем, таких, как Microsoft Cairo и Taligent Pink, будет, по всей видимости, объектно-ориентированным.

Наиболее значительный вклад в объектный подход внесен объектными и объектно-ориентированными языками программирования. Впервые понятия классов и объектов введены в языке Simula 67. Система Flex и последовавшие за ней диалекты Smalltalk-72, -74, -76 и, наконец, -80, взяв за основу методы Simula, довели их до логического завершения, выполняя все действия на основе классов. В 1970-х годах создан ряд языков, реализующих идею абстракции данных: Alphard, CLU, Euclid, Gypsy, Mesa и Modula. Затем методы, используемые в языках Simula и Smalltalk, были использованы в традиционных языках высокого уровня. Внесение объектно-ориентированного подхода в С привело к возникновению языков C++ и Objective С. На основе языка Pascal возникли Object Pascal, Eiffel и Ada. Появились диалекты LISP, такие, как Flavors, LOOPS и CLOS (Common LISP Object System), с возможностями языков Simula и Smalltalk. Более подробно особенности этих языков изложены в приложении.

Первым, кто указал на необходимость построения систем в виде структурированных абстракций, был Дейкстра. Позднее Парнас ввел идею скрытия информации [20], а в 70-х годах ряд исследователей, главным образом Лисков и Жиль [21], Гуттаг [22], и Шоу [23], разработал механизмы абстрактных типов данных. Хоар дополнил эти подходы теорией типов и подклассов [24].

Развивавшиеся достаточно независимо технологии построения баз данных также оказали влияние на объектный подход [25], в первую очередь благодаря так называемой модели "сущность-отношение" (ER, entity-relationship) [26]. В моделях ER, впервые предложенных Ченом [27], моделирование происходит в терминах сущностей, их атрибутов и взаимоотношений.

Разработчики способов представления данных в области искусственного интеллекта также внесли свой вклад в понимание объектно-ориентированных абстракций. В 1975 г. Мински выдвинул теорию фреймов для представления реальных объектов в системах распознавания образов и естественных языков [28]. Фреймы стали использоваться в качестве архитектурной основы в различных интеллектуальных системах.

Объектный подход известен еще издавна. Грекам принадлежит идея о том, что мир можно рассматривать в терминах как объектов, так и событий. А в XVII веке Декарт отмечал, что люди обычно имеют объектно-ориентированный взгляд на мир [29]. В XX веке эту тему развивала Рэнд в своей философии объективистской эпистемологии [30]. Позднее Мински предложил модель человеческого мышления, в которой разум человека рассматривается как общность различно мыслящих агентов [31]. Он доказывает, что только совместное действие таких агентов приводит к осмысленному поведению человека. 

Объектно-ориентированное программирование. Что же такое объектно-ориентированное программирование (object-oriented programming, OOP)? Мы определяем его следующим образом:

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

В данном определении можно выделить три части: 1) OOP использует в качестве базовых элементов объекты, а не алгоритмы (иерархия "быть частью", которая была определена в главе 1); 2) каждый объект является экземпляром какого-либо определенного класса; 3) классы организованы иерархически (см. понятие об иерархии "is а" там же). Программа будет объектно-ориентированной только при соблюдении всех трех указанных требований. В частности, программирование, не основанное на иерархических отношениях, не относится к OOP, а называется программированием на основе абстрактных типов данных.

В соответствии с этим определением не все языки программирования являются объектно-ориентированными. Страуструп определил так: "если термин объектно-ориентированный язык вообще что-либо означает, то он должен означать язык, имеющий средства хорошей поддержки объектно-ориентированного стиля программирования... Обеспечение такого стиля в свою очередь означает, что в языке удобно пользоваться этим стилем. Если написание программ в стиле OOP требует специальных усилий или оно невозможно совсем, то этот язык не отвечает требованиям OOP" [33]. Теоретически возможна имитация объектно-ориентированного программирования на обычных языках, таких, как Pascal и даже COBOL или ассемблер, но это крайне затруднительно. Карделли и Вегнер говорят, что: "язык программирования является объектно-ориентированным тогда и только тогда, когда выполняются следующие условия:

• Поддерживаются объекты, то есть абстракции данных, имеющие интерфейс в виде именованных операций и собственные данные, с ограничением доступа к ним.

• Объекты относятся к соответствующим типам (классам).

• Типы (классы) могут наследовать атрибуты супертипов (суперклассов)" [34].

Поддержка наследования в таких языках означает возможность установления отношения "is-a" ("есть", "это есть", " - это"), например, красная роза - это цветок, а цветок - это растение. Языки, не имеющие таких механизмов, нельзя отнести к объектно-ориентированным. Карделли и Вегнер назвали такие языки объектными, но не объектно-ориентированными. Согласно этому определению объектно-ориентированными языками являются Smalltalk, Object Pascal, C++ и CLOS, a Ada - объектный язык. Но, поскольку объекты и классы являются элементами обеих групп языков, желательно использовать и в тех, и в других методы объектно-ориентированного проектирования.

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

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

В данном определении содержатся две важные части: объектно-ориентированное проектирование 1) основывается на объектно-ориентированной декомпозиции; 2) использует многообразие приемов представления моделей, отражающих логическую (классы и объекты) и физическую (модули и процессы) структуру системы, а также ее статические и динамические аспекты.

Именно объектно-ориентированная декомпозиция отличает объектно-ориентированное проектирование от структурного; в первом случае логическая структура системы отражается абстракциями в виде классов и объектов, во втором - алгоритмами. Иногда мы будем использовать аббревиатуру OOD, object-oriented design, для обозначения метода объектно-ориентированного проектирования, изложенного в этой книге.

Объектно-ориентированный анализ. На объектную модель повлияла более ранняя модель жизненного цикла программного обеспечения. Традиционная техника структурного анализа, описанная в работах Де Марко [35], Иордана [36], Гейна и Сарсона [37], а с уточнениями для режимов реального времени у Варда и Меллора [38] и Хотли и Пирбхая [39], основана на потоках данных в системе. Объектно-ориентированный анализ (или OOA, object-oriented analysis) направлен на создание моделей реальной действительности на основе объектно-ориентированного мировоззрения.

Объектно-ориентированный анализ - это методология, при которой требования к системе воспринимаются с точки зрения классов и объектов, выявленных в предметной области.

Как соотносятся ООА, OOD и OOP? На результатах ООА формируются модели, на которых основывается OOD; OOD в свою очередь создает фундамент для окончательной реализации системы с использованием методологии OOP.

2.2. Составные части объектного подхода

Парадигмы программирования

Дженкинс и Глазго считают, что "в большинстве своем программисты используют в работе один язык программирования и следуют одному стилю. Они программируют в парадигме, навязанной используемым ими языком. Часто они оставляют в стороне альтернативные подходы к цели, а следовательно, им трудно увидеть преимущества стиля, более соответствующего решаемой задаче" [40]. Бобров и Стефик так определили понятие стиля программирования: "Это способ построения программ, основанный на определенных принципах программирования, и выбор подходящего языка, который делает понятными программы, написанные в этом стиле" [41]. Эти же авторы выявили пять основных разновидностей стилей программирования, которые перечислены ниже вместе с присущими им видами абстракций:  

 ∙ процедурно-ориентированный   алгоритмы 

 ∙ объектно-ориентированный   классы и объекты 

 ∙ логико-ориентированный   цели, часто выраженные в терминах исчисления предикатов 

 ∙ ориентированный на правила   правила "если-то" 

 ∙ ориентированный на ограничения   инвариантные соотношения 

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

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

• абстрагирование;

• инкапсуляция;

• модульность;

• иерархия.

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

• типизация;

• параллелизм;

• сохраняемость.

Называя их дополнительными, мы имеем в виду, что они полезны в объектной модели, но не обязательны.

Без такой концептуальной основы вы можете программировать на языке типа Smalltalk, Object Pascal, C++, CLOS, Eiffel или Ada, но из-под внешней красоты будет выглядывать стиль FORTRAN, Pascal или С. Выразительная способность объектно-ориентированного языка будет либо потеряна, либо искажена. Но еще более существенно, что при этом будет мало шансов справиться со сложностью решаемых задач.

Абстрагирование

Смысл абстрагирования. Абстрагирование является одним из основных методов, используемых для решения сложных задач. Хоар считает, что "абстрагирование проявляется в нахождении сходств между определенными объектами, ситуациями или процессами реального мира, и в принятии решений на основе этих сходств, отвлекаясь на время от имеющихся различий" [42]. Шоу определила это понятие так: "Упрощенное описание или изложение системы, при котором одни свойства и детали выделяются, а другие опускаются. Хорошей является такая абстракция, которая подчеркивает детали, существенные для рассмотрения и использования, и опускает те, которые на данный момент несущественны" [43]. Берзинс, Грей и Науман рекомендовали, чтобы "идея квалифицировалась как абстракция только, если она может быть изложена, понята и проанализирована независимо от механизма, который будет в дальнейшем принят для ее реализации" [44]. Суммируя эти разные точки зрения, получим следующее определение абстракции:

Абстракция выделяет существенные характеристики некоторого объекта, отличающие его от всех других видов объектов и, таким образом, четко определяет его концептуальные границы с точки зрения наблюдателя. ...



Все права на текст принадлежат автору: Гради Буч.
Это короткий фрагмент для ознакомления с книгой.
Объектно-ориентированный анализ и проектирование с примерами приложений на С++Гради Буч