Основы объектно-ориентированного проектирования

         

Приложения скрытия потомком


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

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

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

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

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

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

require redeemable

Но тем самым мы изменили интерфейс класса. Все клиенты класса и их бесчисленные потомки мгновенно стали потенциально некорректными. Все их вызовы m.redeem (...) должны быть теперь переписаны как:

if m.redeemable then m.redeem (...) else ... (Кто в мире мог предвидеть это?)... end

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

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

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

class NEW_MORTGAGE inherit MORTGAGE export {NONE} redeem end ...Ни ошибок, ни аномалий не появится в существующем ПО. Если кто-то модифицирует класс клиента, добавив класс с новыми закладными:

m: MORTGAGE; nm: NEW_MORTGAGE ... m := nm ... m.redeem (...)то вызов redeem станет кэтколлом (см. лекцию 17 курса "Основы объектно-ориентированного программирования"), и потенциальная ошибка будет обнаружена статически механизмом, описанным в лекции 17 курса "Основы объектно-ориентированного программирования" при обсуждении типизации.


Содержание раздела