Опубликован: 04.04.2012 | Доступ: свободный | Студентов: 1932 / 30 | Оценка: 4.60 / 4.40 | Длительность: 13:49:00
Лекция 3:

Проектирование семейства классов

< Лекция 2 || Лекция 3: 123 || Лекция 4 >
Аннотация: В лекции рассматриваются новые свойства, связанные с проектированием семейства классов, связанных отношением наследования. Как правило, проектирование семейства начинается с создания отложенного класса (deferred class) – класса, в котором для ряда методов задана только спецификация – контракт и сигнатура, но не задана реализация методов, которую обеспечат потомки класса в соответствии со своей спецификой. В лекции подробно рассматриваются важнейшие для проектирования понятия – "программы с дырами", отложенные методы, переопределение и переобъявление методов наследниками класса.

Отложенные классы и компоненты

Взгляните на список компонентов класса VEHICLE ( рис 2.1).

Здесь можно увидеть компонент movenext. В тексте класса он не появляется, так как наследуется от класса MOVING. Но в тексте MOVING вы найдете следующее объявление

 Компоненты VEHICLE в EiffelStudio

увеличить изображение
Рис. 2.1. Компоненты VEHICLE в EiffelStudio
move_next
    — Переместиться в следующую позицию, согласно расписанию.
  deferred
  end

Это новая форма объявления. Ранее объявление метода имело тело, начинающееся с ключевого слова do c последующими за ним операторами. Объявление компонента как deferred означает, что компонент имеет спецификацию — сигнатуру и контракт, но не имеет реализации. Реализация отложена (отсюда и имя ключевого слова) и возлагается на классы-потомки. Действительно, в классе TAXI можно увидеть реализацию:

move_next
    — Переместиться в следующую позицию, согласно расписанию.
  do
   ... Последовательность операторов...
  end

Реализация учитывает специфику объектов класса TAXI. Класс TRAM обеспечивает свою специфическую реализацию компонента.

Мы говорим, что эти классы делают компонент эффективным. Отныне мы будем иметь два вида компонентов класса: эффективные (спецификация + реализация) и отложенные (только спецификация). От компонентов эти термины переходят на классы и их типы.

Отложенные классы и типы

Если класс С имеет, по крайней мере, один отложенный компонент (либо введенный в классе, но объявленный как отложенный, либо наследуемый отложенный компонент, не ставший эффективным), то сам класс считается отложенным классом.

Тип, базирующийся на отложенном классе, является отложенным типом. Тип или класс является эффективным, если он не отложен (все его компоненты эффективны).

Как обычно, различие между классами и типами связано с универсальностью. Вы могли видеть, что класс LIST является отложенным классом (поскольку он описывает общее понятие списка с реализацией, возложенной на эффективных потомков, таких как LINKED_LIST ). Типы LIST[TAXI] и более общий тип LIST[G] для любого G являются отложенными типами.

Причина, по которой movenext является отложенным компонентом класса MOVING, состоит в том, что на этом уровне:

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

Класс MOVING специфицирует существование компонента, но ответственность за подходящую реализацию лежит на его правильных потомках, таких как TAXI.

Отсутствие реализации по умолчанию в классе MOVING создает проблему: каков будет эффект от вызова m.movenext, если объект, присоединенный к m, имеет тип MOVING или VEHICLE, когда в обоих классах компонент отложен и, следовательно, не имеет реализации? Ответ прост: таких объектов не существует.

Правило создания "Нет отложенному типу"
Целевой тип в операторе создания не может быть отложенным.

Это защищает нас от создания объектов типа MOVING или VEHICLE (например, при попытке create myvehicle ). Благодаря этому становится невозможным вызывать отложенный метод, такой как movenext объектом myvehicle.

Так что, если тип отложен, то нет объектов этого типа. Но при этом можно иметь переменные (и другие сущности и выражения) этого типа, например, myvehicle. Эти переменные можно присоединять к эффективным объектам соответствующих типов, таких как TAXI в нашем примере. Фактически вся идея отложенных компонентов, классов и типов имеет смысл только благодаря полиморфизму и динамическому связыванию.

Поскольку создание требует эффективного типа, при желании создать объект необходимо знать, эффективным или отложенным является соответствующий класс. Чтобы выяснить это, недостаточно знакомства с методами класса, поскольку отложенные компоненты могут наследоваться. Вообще изучение текста классов противоречит принципу скрытия информации. "Быть отложенным" — это ключевое свойство класса, явно им провозглашаемое. Фактически, сразу же после раздела note-класса, поставляющего общую информацию о классе, первое, что говорит класс всему миру, — является ли он отложенным.

Правило отложенного класса
Объявление отложенного класса должно начинаться с ключевых слов deferred class (вместо просто class для эффективных классов).

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

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

В языке Eiffel разрешается объявлять класс как deferred class даже в том случае, когда у класса нет отложенных компонентов. Такой класс также рассматривается как отложенный, расширяя наше определение. Это полезно, если вы проектируете класс как предка семейства классов и хотите избежать создания экземпляров этого класса.

Будем более точными: объявление класса deferred защищает от создания прямых экземпляров. Концепции этой лекции требуют пересмотра понятия "экземпляр". До сих пор экземпляр типа (или лежащего в его основе класса) понимался просто как объект периода выполнения, соответствующий статическому описанию, задаваемому классом. Все это правильно, но теперь требуется ввести уточнения и различать прямой и непрямой случаи.

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

Следствием правила создания "Нет отложенному типу" является то, что у отложенного типа нет прямых экземпляров, как в случае с классами MOVING и VEHICLE. Но оба класса могут иметь экземпляры — прямые экземпляры эффективных потомков, таких, как TAXI.

Эти определения непосредственно отражают концепцию полиморфизма: объявление x типа T означает, что вы хотите, чтобы во время выполнения x обозначал экземпляр этого типа. С введением полиморфизма мы полагаем, что теперь понимаются не только объекты, непосредственно выводимые из T — прямые экземпляры, но и, рекурсивно, экземпляры любого согласованного типа (потомков T ).

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

  • Библиотека EiffelBase, содержащая реализацию структур данных и алгоритмов. Библиотека вводит глобальную таксономию фундаментальных структур, используемых в информатике. На вершине иерархии можно видеть такие классы, как LINEAR (структуры, обходимые линейно) и FINITE (конечные структуры). Ниже по иерархии находятся классы, задающие специфические структуры, которые являются эффективными потомками классов верхнего уровня.
  • Графическая библиотека EiffelVision. Здесь также существует кластер классов, задающих геометрические фигуры, на верхнем этаже которого находится отложенный класс FIGURE, а ниже по иерархии находятся классы, задающие такие фигуры, как круг и квадрат. Таксономия, принятая для геометрических фигур, служит излюбленным примером в учебниках, рассматривающих наследование. Но в данном случае здесь нет ничего академического — это полезная часть важной прикладной библиотеки классов.

Некоторые языки программирования, в том числе такие известные, как C# и Java, предлагают языковую конструкцию, называемую interface (это понятие рассматривается в приложениях, посвященных этим языкам). Класс, объявленный с таким ключевым словом, называется интерфейсом, и его главная особенность состоит в том, что все его методы отложены (не имеют контрактов) и должны быть реализованы потомками интерфейса1 Наряду с интерфейсами в языке C# существует понятие абстрактного класса, совпадающего по концепции с понятием отложенного класса и использующегося в тех же целях, что и отложенный класс. Интерфейсы C# используются в интересах множественного наследования..

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

Мощным приемом, включающим частично отложенные классы, является образец проектирования, называемый "Программа с дырами", в котором эффективные методы вызывают отложенные. Такой метод описывает некоторую общую схему решения задачи, оставляя часть деталей для реализации потомкам. Этот подход позволяет комбинировать повторное использование и адаптивность к конкретным условиям. Типичным примером, встречавшимся многократно, служит стандартная схема повторения (итерирования), где некоторая операция выполняется над всеми элементами последовательной структуры, подобной списку. Если ее применить к операции поиска, то она выглядит так:

search (v: G)
    Передвинуться к первой позиции (или после текущей), где появляется v.
    Если такой позиции нет, то убедиться, что переменная exhausted будет
    иметь значение true.
  do
    from until v = item loop forth end
  end

Классы EiffelBase представляют списки и другие подобные им структуры как потомков класса LINEAR, где и появляется приведенный код (в более полной версии учитывается различие между равенством ссылок и равенством объектов). Реализация forth, однако, зависит от реализации, выбранной для списков (использующей массив, ссылочные структуры и так далее). Поэтому в классе LINEAR метод forth является отложенным:

forth
    — Переместить курсор к следующей позиции
  require
    in_range: not after
  deferred
  ensure
    increased: index = old index + 1
  end

Обратите внимание на предусловие и постусловие: как обсуждается ниже, контракты полностью применимы к отложенным компонентам и классам.

Каждый эффективный потомок LINEAR реализует forth. Для списка, построенного на массиве, где курсор задается индексом, достаточно выполнить присваивание index:= index + 1. Для связного списка ( LINKEDLIST, TWO_WAY_LIST ) детали более сложны. Но методу search нет дела до деталей: все, что ему нужно, — это вызвать forth, зная спецификацию, заданную контрактом, а не реализацией.

Имя "Программа с дырами" отражает подход к возрастающему конструированию ПО. На каждом уровне абстракции используется вся известная на этом уровне информация: та, что может быть полностью реализована (эффективные компоненты, не включающие вызовов отложенных методов), те методы, которые могут быть только специфицированы, и те, которые допускают реализацию, использующую вызовы отложенных методов, подобные search в LINEAR. Мы можем рассматривать результат как частично сконструированную программу, дыры которой должны быть заполнены в процессе уточнения при конструировании потомков.

Этот подход — один из наиболее важных вкладов ОО-методологии в упорядоченное конструирование систем, особенно больших систем.

Переопределение

Когда класс реализует отложенный компонент, он задает первую реализацию метода, который до сих пор у предков имел только спецификацию. Чтобы использовать в дальнейшем преимущества динамического связывания и сделать архитектуру еще более гибкой, разрешается потомку дать свою собственную реализацию, несмотря на то, что родитель уже задал родительскую реализацию компонента. Мы будем говорить, что класс переопределяет ( redifine в Eiffel, override в C++, C#) компонент. Переопределение дополняет только что изученный механизм эффективизации:

 Формы переопределения

Рис. 2.2. Формы переопределения

На диаграмме наиболее правый случай показывает, что при наследовании отложенного компонента можно не только сделать его эффективным (следуя ветви, помеченной как Effecting), но также изменить его, оставляя на время отложенным. Это происходит в том случае, когда наследник меняет спецификацию метода — сигнатуру или контракт, — что также рассматривается как переопределение. В дополнение к списку вариантов отметим возможность отмены реализации (Undefinition), при которой реализация родителя забывается, метод снова становится отложенным и готов начать новую жизнь.

Термин переобъявление покрывает все эти случаи.

< Лекция 2 || Лекция 3: 123 || Лекция 4 >
Надежда Александрова
Надежда Александрова

Уточните пожалуйста, какие документы для этого необходимо предоставить с моей стороны. Курс "Объектно-ориентированное программирование и программная инженения".