Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
Поэтому вышеприведенный текст является не столько подпрограммой, а шаблоном подпрограммы, который можно превратить в действующую подпрограмму, лишь после уточнения фрагментов, написанных заглавными буквами.
Повторно использовать или переделать? (The reuse-redo dilemma)
Наличие всех этих вариантов выдвигает на первый план проблемы, возникающие при любой попытке размышлять над созданием модулей общего назначения в заданной прикладной области: как же воспользоваться наличием единого шаблона для согласования с таким большим числом различных вариантов? Это не только проблема реализации: почти так же трудно специфицировать модуль таким образом, чтобы модули-клиенты могли рассчитывать на взаимодействие с ним, не располагая его реализацией.
По этим соображениям обречены на неуспех простые решения проблемы повторного использования. Ввиду многосторонности и изменчивости ПО - не зря оно называется "soft - модули, не обладающие "гибкостью", не могут претендовать на возможность повторного использования.
"Замороженность" модуля приводит к дилемме - повторно использовать или переделать: повторно использовать модуль таким, какой он есть, или заново все переделать. Оба подхода слишком ограничительные. Типичная ситуация, когда существует модуль, обеспечивающий лишь частичное решение текущей задачи, и требуется адаптация модуля к конкретным потребностям. В этом случае желательно и повторно использовать и переделать: кое что повторно использовать, а кое что переделать - или, лучше всего, многое повторно использовать, а совсем немного переделать. Без способности объединения возможностей повторного использования и адаптации, методы повторного использования не могут удовлетворять практическим потребностям разработки ПО.
Поэтому не случайно почти любое обсуждение проблем повторного использования в этой книге затрагивает и проблему расширяемости (что приводит к охватывающему оба эти понятия термину "модульность", являющегося предметом обсуждения в предыдущей лекции). Всякий раз, когда вы начнете искать ответы на одно из этих требований, вы тут же столкнетесь и с другим требованием.
Такая взаимозависимость между повторным использованием и расширяемостью отмечалась ранее при обсуждении принципа Открыт-Закрыт. (См. "Принцип Открыт-Закрыт", лекция 3)
Поиску подходящего представления модуля посвящена оставшаяся часть этой лекции и несколько следующих лекций. Нам предстоит согласовать между собой возможность повторного использования и расширяемость, закрытость и открытость, постоянство и изменчивость. Нам следует удовлетворить сегодняшние потребности и попытаться отгадать, что же понадобится завтра.
Пять требований к модульным структурам
Как же найти такие модульные структуры, которые позволят создать компоненты, непосредственно готовые к повторному использованию, и, в то же время, допускающие возможность их адаптации?
Задача табличного поиска и шаблон подпрограммы has иллюстрируют жесткие требования, предъявляемые к любому решению. Можно воспользоваться этим примером для выяснения, что же следует предпринять для перехода от обнаружения относительно нечеткой общности вариантов к реальному набору повторно используемых модулей. Такой анализ выявляет пять важных проблем:
[x]. Изменчивость Типов (Type Variation).
[x]. Группирование Подпрограмм (Routine Grouping).
[x]. Изменчивость Реализаций (Implementation Variation).
[x]. Независимость Представлений (Representation Independence).
[x]. Факторизация Общего Поведения (Factoring Out Common Behaviors).
Изменчивость Типов (Type Variation)
Шаблон подпрограммы has предполагает, что таблица содержит объекты типа ELEMENT. При уточнении этой подпрограммы в применении к частному случаю можно использовать конкретный тип, например INTEGER или BANK_ACCOUNT, для таблицы целых чисел или банковских счетов.
Но это не совсем то, что требуется. Повторно используемый модуль поиска должен быть применим ко многим различным типам элементов без того чтобы пользователи вынуждены были производить "вручную" изменения в тексте программы. Другими словами, необходимо средство для описания модулей, в которых типы выступают в роли параметров (type-parameterized), или короче - родовых (полиморфных) модулей. Универсальность или полиморфность (genericity) (способность модулей быть родовыми) окажется важной частью ОО-метода; обзор этой концепции дается далее в этой лекции. (См. "Универсальность" ("Genericity"), лекция 4)
Группирование Подпрограмм (Routine Grouping)
Шаблон подпрограммы has, даже если его полностью детализировать и ввести параметризацию типа, все еще не будет пригоден в качестве повторно используемого компонента. Поиск в таблице зависит от того, как таблица создавалась, как в нее включаются элементы, как они удаляются. Отдельно взятая программа поиска - это еще не модуль повторного использования. Самодостаточный, повторно используемый модуль должен включать множество подпрограмм, обеспечивающих каждую из упомянутых операций - создание, включение, удаление, поиск.
Эта идея лежит в основе формирования модуля как "пакета", что имеет место в языках с инкапсуляцией таких как: Ada, Modula-2 и родственных им языках. Более подробно об этом будет сказано ниже.
Изменчивость Реализаций (Implementation Variation)
Шаблон has является весьма общим; и, как мы уже убедились, на практике имеется широкий выбор соответствующих структур данных и алгоритмов. Нельзя ожидать, что один модуль сможет обеспечить работу в столь разнообразных условиях, - он оказался бы просто огромным. Для охвата всех возможных реализаций требуется семейство модулей.
Общая методика создания и применения повторно используемых модулей должна поддерживать идею семейства модулей.
Независимость Представлений
Общая структура повторно используемого модуля должна позволять модулям-клиентам определять свои действия при отсутствии сведений о реализации модуля. Это требование называется Независимостью Представлений.
Предположим, что модулю-клиенту C некоторой прикладной системы (управления ресурсами банка, компилятора, системы географической информации) необходимо определить, содержится ли некоторый элемент x в некоторой таблице t (вкладов, слов языка, городов). Независимость Представлений для C означает возможность получить такую информацию с помощью обращения к подпрограмме
present := has (t, x)
не зная, какой вид имеет таблица t во время этого обращения. Автору модуля C нужно лишь знать, что t-это таблица из элементов определенного типа, и что x означает объект того же типа. Ему безразлично, является ли t деревом двоичного поиска, хеш-таблицей или связным списком. Он должен иметь возможность сосредоточиться на своей задаче управления активами, компиляции или географии.
Выбор подходящего алгоритма поиска, основанного на реализации таблицы t, является делом лишь того модуля, который организует эту таблицу.
Модуль-клиент C, содержащий упомянутое обращение к подпрограмме, мог бы получить t от одного из своих собственных клиентов (в виде аргумента вызова подпрограммы). Тогда для C имя t является лишь абстрактным идентификатором структуры данных, к детальному описанию которой он и не может иметь доступа.
Можно рассматривать Независимость Представлений как расширение правила Скрытия Информации (инкапсуляции), существенное для беспрепятственной разработки больших систем: решения по реализации могут часто изменяться, и клиенты должны быть защищены от этого (См. "Скрытие информации", лекция 3). Но требование Независимости Представлений идет еще дальше. Если обратиться к его полномасштабным последствиям, то оно означает защиту клиентов модуля от изменений не только во время жизненного цикла проекта, но и во время выполнения - а это намного меньший временной интервал! В рассматриваемом примере, желательно, чтобы подпрограмма has адаптировалась автоматически к виду таблицы t во время выполнения программы, даже если этот вид изменился со времени последнего обращения к подпрограмме.
Выполнение требования Независимости Представлений поможет также реализовать связанный с ним принцип Единственного Выбора, сформулированный при обсуждении модульности, который предписывает избегать ситуаций, связанных с разбором вариантов, например