Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Объектно-ориентированное программирование.PDF
Скачиваний:
208
Добавлен:
01.05.2014
Размер:
3.64 Mб
Скачать

converted to PDF by BoJIoc

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

2.7.3. Зацепление и связность

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

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

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

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

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

2.7.4. Интерфейс и реализация модуля — принципы Парнаса

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

Все это справедливо вне зависимости от того, как конкретно реализован внутри Recipe Database просмотр базы данных.

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

converted to PDF by BoJIoc

Вид со стороны реализации это «изнанка», видимая только тем, кто работает над конкретной компонентой. Здесь определяется, как компонента выполняет задание.

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

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

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

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

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

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

2.8. Формализация интерфейса

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

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

2.8.1. Выбор имен

Имена, связанные с различными действиями, должны тщательно подбираться. Шекспир сказал, что переименование не меняет сути объекта 1 , но определенно не все имена будут вызывать в воображении слушателя одинаковые мысленные образы.

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

converted to PDF by BoJIoc

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

Были предложены следующие положения общего характера, регулирующие этот процесс

[Keller 1990]:

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

Применяйте заглавные буквы или символы подчеркивания для того, чтобы отметить начало нового слова в составном имени: CardReader или Card_reader вместо нечитаемого cardreader.

Тщательно проверяйте сокращения. Сокращение, ясное для одного человека, может быть загадочным для другого. Обозначает ли имя TermProcess последний процесс в цепочке (terminal process), или нечто, что прекращает выполнение процесса (terminate process), или же процесс, связанный с терминалом компьютера?

Избегайте многозначности имен. Имя empty для функции обозначает ли оно проверку того, что некоторый объект пуст, или же она удаляет все значения из объекта (делает его пустым)?

Не используйте цифры в именах. Их легко спутать с буквами (0 как O, 1 как l, 2 как Z, 5 как S).

Присваивайте функциям, которые возвращают логические (булевские) значения, такие имена, чтобы было ясно, как интерпретировать true и false. Например, PrinterIsReady ясно показывает, что значение true соответствует принтеру в рабочем состоянии, в то время как PrinterStatus является гораздо менее точным.

1 «Что значит имя? Роза пахнет розой, хоть розой назови ее, хоть нет. Ромео под любым названьем был бы тем верхом совершенств, какой он есть». — Вильям Шекспир, «Ромео и Джульетта», действие II, сцена 2 (пер. Бориса Пастернака).

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

Как только для всех действий выбраны имена, CRC-карточка для каждой компоненты переписывается заново с указанием имен функций и списка формальных аргументов. Пример CRC-карточки для компоненты Date приведен на рис. 2.5. Что осталось не установленным, так это то, как именно каждая компонента будет выполнять указанные действия.

Необходимо еще раз «прокрутить» сценарий более детально, чтобы гарантировать, что

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