Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

J " " 10 I

Часть I <^ '"', - '"imfi в ГФ.

о C-^+

 

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

 

контроля качества в рамках процесса.

 

 

Кроме того, чем дальше продвигается реализация проекта, тем труднее полу­

 

чить от пользователей осмысленные отзывы. В терминологии им ориентироваться

 

все сложнее (она становится более компьютерно-ориентированной), а в схемах

 

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

 

екта часто делается формально, "под копирку".

 

Преимундество такого подхода — в хорошо спроектированной структуре с чет­

 

ко определенными для каждого разработчика ролями, конкретными требованиями

 

(что должно получиться на выходе по завершении конкретной стадии) и сроками.

 

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

 

предлагается ряд инструментальных средств. Это особенно важно д/1я крупных

 

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

 

Опыт, накопленный при реализации одного проекта, помогает планировать следу-

 

юш,ие аналогичные проекты.

 

 

Недостаток — излишний формализм,

замена персональной ответственности

 

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

Быстрое прототипирование

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

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

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

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

Глава 1 • Объектно-ориентированный подход: нто это такое?

11

все равно создаются вручную. Разработчики сталкиваются с трудностями при документировании этих взаимосвязей, необходимом для того, чтобы работающие над другими частями системы поняли, чего от них ждут, и знали обо всех ограниче­ ниях. У программистов, занимающихся сопровождением ПО, также возникают трудности понимания сложных (и плохо документированных) взаимосвязей.

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

Выход третий:

разработка сложного и подробного языка

Раньше языки программирования (Фортран, Кобол, АПЛ, Бейсик и даже С) разрабатывались для упрощения написания программного кода. Эти языки про­ граммирования были относительно компактными, сжатыми и простыми в изучении. Считалось, что писать на них могут специалисты, имеющие самый незначительный опыт программирования. Позднее в разработке языков программирования прои­ зошел четкий сдвиг. В современных языках, включая С, Ada и Java, применяется иной подход. Они далеко не компактны и достаточно сложны в изучении. Написан­ ные на этих языках программы длиннее аналогичных программ на традиционных языках. На программистов свалилось бремя определений, описаний и других вспомогательных элементов кода.

Такая "многословность" языков повлияла и на согласованность программного кода. Если программист использует в разных частях программы несогласованный код, компилятор обнаруживает это и вынуждает его устранить данную несогласо­ ванность. В прежних языках компилятор предполагал, что согласованность вводится преднамеренно для достижения некоей известной программисту цели. Разработчики языков и компиляторов считали: "Зачем думать за программиста?". В таких допускающих ошибки языках поиск ошибок часто требовал достаточно сложного тестирования на этапе выполнения, а некоторые ошибки все равно оставались невыловленными. В современных языках несогласованность кода интерпретируется как синтаксическая ошибка, выявляемая еще до выполнения программы. Это важное преимущество, но оно затрудняет написание программы.

Еще одно достоинство "многословности" языка — возможность лучше выра­ зить намерения программиста. В традиционных языках программисту, занимаю­ щемуся сопрово>вдением программного кода, часто приходилось гадать, что именно задумывал разработчик ПО. Чтобы программу можно было читать, в исходном коде требовались подробные комментарии, но мало кто из разработчиков утруж­ дал себя адекватным комментированием программ — часто на это просто не хва­ тало времени. В современных языках программа фактически самодокументируема. "Избыточные" описания уменьшают необходимость в комментариях и облегчают сопровождение программы, так как намерения разработчика более очевидны. Это новая тенденция в отрасли, и мы рассмотрим примеры программного кода, где реализуется данный подход.

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

12 I Часть i * Введение в программирование но С^^^*-

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

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

Объектно-ориентированный подход: что он дает и какой ценой?

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

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

Может быть, объектно-ориентированный подход — просто модно? Не придет ли со временем ему на смену что-нибудь еще? Дает ли он реальные преимущест­ ва? Имеет ли объектно-ориентированный подход какие-то недостатки, требует ли компромиссных решений?

Откровенно говоря, при написании небольших программ ООП (и применение классов C+ + ) не дает никаких преимуществ. Преимущества объектно-ориенти­ рованного подхода перевешивают его недостатки только при реализации крупных программных проектов.

Если говорить о сложности программы, то нужно иметь в виду следующее:

Сложность самого приложения (что именно оно делает для пользователей)

Сложность реализации программы (решений разработчиков и программистов)

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

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

Глава 1 • Объектно-ориентированный подход: что это такое?

[ 13

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

Чем занимается проектировщик?

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

Проектирование начинается после получения ответов на все эти вопросы. Обобщенно говоря, проектирование — это совокупность решений о том:

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

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

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

проектирования, обеспечиваюидие конкретные уровни модульности.

Если подумать, сказанное применимо не только к проектированию ПО, но и к лю­ бой творческой деятельности, будь то написание музыкальной композиции, книги, письма другу или картины. В любом случае нужно решить, из каких компонентов будет состоять "продукт", как эти компоненты соотносятся друг с другом и какова роль каждого компонента в достижении общей цели. Чем сложнее задача, тем важнее проектирование для достижения качественного результата. Простое по­ слание по электронной почте не требует тш,ательного планирования. Совсем другое дело — составление руководства пользователя по программному продукту.

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

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

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

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

14

Часть I * ВвеАен1^в в програтмтроваиие на С+^

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

Разработчики ПО применяют несколько критериев, помогающих им свести к минимуму сложность и взаимозависимость фрагментов программного кода. Тра­ диционными критериями качества ПО являются сцепление (cohesion) и связность (coupling). Современные объектно-ориентированные критерии — сокрытие ин­ формации (ограничение доступа к ней) и инкапсуляция.

Качество проекта: сцепление

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

Для функции с хорошим сцеплением легко придумать имя — обычно это со­ ставные термины, содержащие один глагол (действие) и одно существительное (объект): например, insert Item, fincJAccount (если имя соответствует содержанию, что далеко не всегда имеет место). Для функций со слабым сцеплением использу­ ется несколько глаголов или существительных, например findOrinsertItem, когда функция находит элемент в наборе или вставляет его, когда элемент не найден (если, конечно, функции намеренно не присваивают имя f indltem, чтобы скрыть промахи разработчика).

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

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

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

Качество проекта: связность

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

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

Глава 1 • Объектно-ориентированный подход: что это такое?

[ 15

|

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

Избавиться от избыточной связности также можно перепроектированием. Нужно тидательно продумать, что должна делать каждая функция. Если часть операций над данными выполняется в одной функции, а часть — в другой, то проектировщику следует подумать о выделении обеих частей в одну функцию. Это уменьшит сложность проекта, не увеличивая числа функций программы. На­ пример, перенос обработки ошибок из функции findltem в processTransaction устранит необходимость передачи в findltem текста с сообщением об ошибке и флага обратно в processTransaction.

Внимание Сцепление и связность подробнее обсуждены при рассмотрении примеров программного кода в главе 9.

На данном этапе читателям было бы полезно взглянуть на программу С+4- и проанализировать ее с точки зрения сцепления, связности и разделения того, что должно быть в одном модуле.

Качество проекта:

связывание вместе данных и функций

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

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

Так в чем же выгода связывания данных и операций? Проблема функциональ­ ного подхода к проектированию программы в том, что "независимые" функции связываются с другими функциями через данные. Например, одна функция мо­ жет присваивать значение переменной, а другая — использовать это значение (findltem устанавливает значение индекса, а processTransaction данный индекс использует). Таким образом, создается зависимость между двумя функциями (и, вероятно, между другими функциями тоже).

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

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

16 Часть I # Bee,: ^ в прогрс

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

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

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

На рис. 1.5 показана взаимосвязь между двумя объектами — сервером и кли­ ентом. Каждый объект содержит данные, методы и имеет границу. Все, что нахо­ дится внутри границы, является закрытым (private) и для других частей программы недоступно, а все, что лежит вне границы,— общедоступно (public), т. е. допуска­ ет обращение из других частей программы. Находящиеся внутри границы данных невидимы извне. Методы (функции) могут быть частично внутри, а частично вне границы. Внешняя часть представляет интерфейс метода с клиентами, а внутрен­ няя — реализацию кода, скрытого от клиента.

««Ый доступ к данны_м_серверадля_мегода

Данные сервера J

Локальный

 

 

доступ

Методы

 

методов

 

к данным

сервера

Методы

 

 

ОБЪЕКТ-СЕРВЕР

ОБЪЕКТ-КЛИЕНТ

клиента

 

Рис. 1.5. Взаимосвязь между объектами сервером и клиентом

Глава 1 • Объектно-ориентированный подход: что это такое?

|

17

|

Внимание данная нотация была разработана Гради Бучем для проектирования и программирования на языке Ada. Она оказалась очень полезной

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

Качество проекта:

сокрытие информации и инкапсуляция

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

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

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

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

Всем ли компонентам в описании заказчика присваиваются значения

Присваиваются ли значения именно здесь и кому присваиваются (только компонентам описания заказчика или другим данным)

Ответы на эти вопросы могут потребовать времени и усилий. Кроме того, увели­ чивается с/южность программы.

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

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

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

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

18

Часть I ^ BeeAeni^e в орогр'-' ' -. г-ошаишв но C++

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

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

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

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

Проблемы проектирования: конфликты имен

Менее критичными, но очень важными являются конфликты имен в большой программе. В программе С+Ч- имена функций должны быть уникальными. Если один разработчик выбирает имя функции findltem, то другой не может назвать так какую-либо еще функцию программы. На первый взгляд проблема проста. Любой, кому потребуется такое имя, может слегка модифицировать его, напри­ мер: findlnventoryltem или findAccountingltem.

Многих разработчиков очень раздражает такое ограничение. Но это не просто необходимость придумывать новые имена. Это проблема дополнительных согласо­ ваний между разработчиками.

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

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

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

Проблемы проектирования: инициализация объектов

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

Глава 1 • Объектно-ориентированный подход: что это такое?

[

19

|

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

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

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

Так что же такое объект?

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

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

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

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

Соседние файлы в предмете Программирование на C++