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

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


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

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

Рассмотрим класс и его подпрограммы, имеющие как предусловие, так и постусловие:


Рис. 16.1.  Подпрограмма, клиент и контракт

На рис. 16.1 показан клиент C класса A. Чтобы быть клиентом, класс C, как правило, включает в одну из своих подпрограмм объявление и вызов вида:

a1: A ... a1.r

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

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

if a1.

then a1.r check a1.? end -- постусловие должно выполняться ... Инструкции, которые могут предполагать истинность a1.. ... end

(Как отмечалось при обсуждении утверждений, не всегда требуется проверка: достаточно, с помощью if или без него, гарантировать выполнение условия a перед вызовом r. Для простоты будем использовать if-форму, игнорируя предложение else.)

Обеспечив соблюдение предусловия, клиент C рассчитывает на выполнение постусловия a1.? при возврате из r.

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


Что происходит, когда вводится наследование?


Рис. 16.2.  Подпрограмма, клиент, контракт и потомок

Пусть новый класс A' порожден от A и содержит повторное объявление r. Как он может, если вообще может, заменить прежнее предусловие
новым ?, а прежнее постусловие ? - новым
?

Чтобы найти ответ, рассмотрим обязательства клиента. В вызове a1.r цель a1 может - в силу полиморфизма - иметь тип A'. Однако C об этом не знает! Единственным объявлением a1 остается исходная строка

a1: A

где упоминается A, но не A'. На деле C может использовать A', даже если его автор не знает о наличии такого класса. Вызов подпрограммы r может произойти, например, в процедуре C вида:

some_routine_of_C (a1: A) is do ...; a1.r;... end

Тогда при вызове some_routine_of_C из другого класса в нем может использоваться фактический параметр типа A', даже если в тексте клиента C класс A' нигде не упоминается. Динамическое связывание как раз и означает тот факт, что обращение к r приведет в этом случае к использованию переопределенной версии A'.

Итак, может сложиться ситуация, в которой C, являясь только клиентом A, фактически во время выполнения использует версии компонентов класса A'. (Можно сказать, что C - "динамический клиент" A', хотя в тексте C об этом и не говорится.)

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

if a1.
then a1.r end

если a1 полиморфно присоединена к объекту типа A', инструкция вызовет подпрограмму, ожидающую выполнения ? и гарантирующую выполнение
, в то время как клиент получил указание соблюдать
и ожидать выполнения ?. Налицо возможное расхождение во взглядах клиента и поставщика на контракт.


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