Подпрограммы, функциональная абстракция, скрытие информации
Чтобы держать сложность под контролем, привлечем еще один проверенный временем прием решения задач: выделение подзадач. Подзадача — это задача, чье решение может помочь в решении других задач. Если мы умеем решать подзадачу и умеем представить это решение в виде элемента управления, простого или сложного, то можно дать имя этой подзадаче и использовать новый элемент под этим именем. Этот прием известен как функциональная абстракция, а соответствующий программный механизм — как подпрограмма (routine).
8.1. Выводимость "снизу вверх" и "сверху вниз"
Почему полезно выделять подзадачи? Можно привести два дополняющих ответа.
- При решении задачи можно выделить подзадачу, для которой решение уже известно. Тогда мы просто вставляем известное решение в решение большой задачи. Это и характеризует подход "снизу вверх" использования подзадач: используем то, что уже нам известно, для решения новых задач, большего размера. Такой стиль построения решения характерен для физиков и инженеров. Инженер, анализируя электрическую систему, опишет ее модель системой дифференциальных уравнений известного типа, затем использует известные методы: решения таких уравнений и выведет свойства своей системы.
- В других случаях мы осознаем, что часть решения нашей задачи представляет собой отдельную задачу, которая, мы надеемся, может быть более простой, чем исходная задача. Этот взгляд может быть полезен, даже если мы не знаем решения созданной подзадачи, поскольку он позволяет нам независимо рассматривать отдельные части большой задачи. Мы можем предположить существование решения подзадачи и использовать его для решения задачи в целом. Получив решение в целом, можно вернуться к подзадачам и разыскивать их решение. Это и определяет подход "сверху вниз": начинаем работать над общей целью и выделяем подцели, которые должны быть решены независимо. Разработка "сверху вниз" известна также как принцип "разделяй и властвуй". Мы уже встречались с этим приемом, когда использовали псевдокод, позволяющий неформальным образом описать части программы, которые позже уточнялись в процессе детализации.
Независимо от способа разработки — "снизу вверх" или "сверху вниз" — использование подзадач является формой абстракции: игнорировать специфику частной ситуации, рассматривая ее как экземпляр общей схемы.
В программировании соответствующая конструкция, задающая решение подзадачи, известна как подпрограмма.
Почувствуй терминологию
У английского термина "routine" есть синонимы - "subprogram" и "subroutine".
Подпрограмма может возвращать результат, и тогда ее называют функцией. Подпрограмма, не возвращающая результат, называется процедурой. Оба эти термина часто используются для ссылок на подпрограммы любого типа. В языке С, например, применяется единственный термин - "функция". В добавлении к этой терминологической путанице в ОО-языках добавляют новый термин - "метод" (method), означающий то же, что и подпрограмма, но добавляющий дополнительную сложность из-за совпадения с общим употреблением этого слова. Вот пара примеров: "Нет никакого метода в написанных им методах" или "От его методов можно сойти с ума1В Eiffel для составляющих класса используется единый термин "feature", который мы, за неимением лучшего, переводим как "компонент". Калька слова feature (фича) (используемая в профессиональном жаргоне) кажется неприемлемой. Компонент - feature - может быть подпрограммой (routine) или переменной, и тогда в Eiffel он называется атрибутом (attribute). Несмотря на справедливую критику термина "метод", именно он, а не термин "подпрограмма", используется при переводе, когда речь идет о методах (routine) класса.".
Подпрограммы появляются как в разработке "сверху вниз", так и "снизу вверх". При проектировании "снизу вверх" они поддерживают повторное использование: можно получить серьезный выигрыш, если вы или кто-то другой полезную для вас алгоритмическую схему превратили в подпрограмму. При подходе "сверху вниз" можно использовать вызовы подпрограмм, которые представляют хорошо определенные элементы разработки, отложив написание самих подпрограмм. Это подобно написанию псевдокода, но в более структурированном варианте, поскольку на момент вызова необходимо дать точное имя и определить интерфейс вызываемой подпрограммы.
8.2. Подпрограммы как методы (routines as features)
Подпрограмма характеризует алгоритм (операцию), применимый ко всем экземплярам класса. Как таковая, она является одним из двух видов характерных компонентов класса, задавая метод класса. Другой характеристикой класса, изучаемой в следующей лекции, является атрибут.
Будучи методом, подпрограмма имеет:
- объявление, которое появляется в тексте класса в разделе feature и описывает все свойства подпрограммы. Объявление подпрограммы также называется ее реализацией;
- интерфейс, который выделяет только подмножество свойств подпрограммы, а именно те, которые интересны клиентам подпрограммы. Интерфейс метода отображается в контрактном облике класса.
Мы уже сталкивались со многими подпрограммами, зная их как методы: класса. Например:
- наш самый первый метод, explore в классе PREVIEW, является подпрограммой. Это же верно и для метода из предыдущей лекции traverse, который в разных вариантах предлагался для реализации;
- при изучении того, как использовать класс, зная его интерфейс, мы рассматривали класс STATION, который в разделе feature объявлял методы — команду remove_all_segments и запрос i_th (в этом же разделе у этого класса были заданы атрибуты, такие как southend и count).
В случае самостоятельного задания метода необходимо дать полное объявление подпрограммы, для использования метода достаточно знания его интерфейса, например:
remove_all_segments — Удалить все станции за исключением конечной, юго-западной. ensure only_one_left: count = 1 both_ends_same: south_end = north_end
Полный текст подпрограммы можно видеть, изучая класс STATION. Теперь мы приступаем к изучению того, как самостоятельно писать объявления методов.
8.3. Инкапсуляция (скрытие) функциональной абстракции
Последний пример изучения условного оператора дает хороший пример определения "функциональной абстракции" в форме подпрограммы. Внешний цикл, появляющийся в методе traverse из класса ROUTES, записан как
from ... invariant ... variant ... until ... loop if Line8.item .is_exchange then show_blinking_spot (Line8.item.location) elseif Line8.item.is_railway_connection then show_big_red_spot (Line8.item.location) else show_spot (Line8.item.location) end Line8.forth endЛистинг 8.1.
В этой записи нас беспокоит не только повторение кода, но и отсутствие распознавания того факта, что действия применяются к одному и тому же объекту — Line8.item. Это свойство становится более ясным, если мы выделим условную структуру в самостоятельный метод. Цикл тогда будет выглядеть так:
from ... invariant ... variant ... until ... loop show_station( Line8.item) Line8.forth endЛистинг 8.2.
Он использует новый метод show_station, чье объявление появляется в том же классе ROUTES:
show_station (s: STATION) -Подсветить s в форме, соответствующей ее статусу require station_exists: s /= Void do if s.is_exchange then show_blinking_spot (s.location) elseif s.is_railway_connection then show_big_blue_spot (s.location) else show_spot (s.location) end end