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

Наследование и полиморфизм

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

Мир (готовы ли вы уже в самом начале лекции погрузиться в водовороты жизни?) полон беспорядка. Возможно, Природа ненавидит беспорядок, а может быть — и нет, я в этом не уверен. Ваша точка зрения во многом зависит от того, кого вы читали — Платона, Аристотеля, Канта. Наука определенно борется с беспорядком. Здравые рассуждения о мире требуют порядка.

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

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

Чтобы стать науками, ботанике и зоологии нужен был Линней, который в 18-м столетии создал эффективную классификацию живых существ. Биологическая таксономия говорит нам, что дельфины принадлежат виду, называемому по латыни Delphinius delphis, они включены в род Delphinius, который сам является частью — пропуская некоторые уровни классификации — отряда китовых из класса млекопитающих, принадлежащего, вне всякого сомнения, царству животных1 Основная классификация Линнея обычно содержит 7 уровней: царство -> тип -> класс -> отряд -> семейство -> род -> вид. Подробная классификация может содержать более 30 уровней, вводя имена промежуточных уровней и создавая уровни с использованием приставок -подтип, надкласс, суперкласс, подотряд и так далее. Для именования объектов используются два последних уровня род и вид. Мы, люди, согласно этой классификации относимся к Homo sapiens (род - человек, вид - разумный). Как и дельфины, мы входим в класс млекопитающих из царства животных.

Объекты в этом случае естественные, а классификация искусственная.

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

Объекты в математике — числа, функции, последовательности — являются искусственными, плодами человеческого воображения. Эварист Галуа в начале 19-го века, а затем Георг Кантор и другие для группировки таких математических объектов предложили абстрактные математические структуры, объединяя объекты в категории, создающие иерархическую структуру. Объекты, для которых определена единственная ассоциативная операция и выделен объект с особыми свойствами, называемый единицей, образуют моноид. Примером моноида могут служить строки, где в качестве операции рассматривается конкатенация строк, а единицей является пустая строка. Другим примером являются целые неотрицательные числа с операцией сложения и нулем в качестве единицы.

Если в моноид добавить новое свойство, получим группу. Группа является моноидом, в котором у каждого элемента существует обратный элемент. Примером группы могут служить множество целых чисел с операцией сложения, с нулем в качестве единицы группы, и для каждого элемента x в группе существует обратный элемент —x. Добавив в группу еще одну операцию, можем получить кольцо. Примером кольца могут служить целые числа с операциями сложения и умножения. Добавление новых свойств позволяет получить поле (пример — поле действительных чисел).

Подобно математикам, мы, программисты, имеем дело с абстрактными объектами — творениями нашего воображения — и не можем обвинять никого, кроме себя, при обнаружении беспорядка в мире наших объектов. И нам, как и всем, нужно преобразовать беспорядок, создавая подобие порядка. Возможно, нам это нужно больше, чем кому-либо, поскольку мы являемся чемпионами в создании энтропии, ибо наши программы способны создать самую немыслимую путаницу, которая только возможна. Говорят, что "человеку свойственно ошибаться, но путаницу несомненно создает компьютер" ; добавим к этому: "или компьютерный программист".

Чтобы справиться с беспорядком, подобно всем наукам, мы можем использовать таксономию. Мы можем объединить наши объекты в категории и рассматривать иерархические отношения между этими категориями. Как дельфин, будучи млекопитающим, является позвоночным животным, как поле в математике является кольцом, так и такси, моделируемое в нашей программе, является транспортным средством и как таковое является движущимся городским объектом. Пешеход также является движущимся городским объектом, но не является транспортным средством. Наследование позволит нам делать выводы о таких отношениях, как "является" ("is—a") и использовать таксономии для структурирования нашего ПО.

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

Такси и транспортные средства

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

Наследуемые компоненты

Класс TAXI из TRAFFIC обеспечивает — вы можете это проверить — такой компонент, как

take (from_location,to_location: LOCATION)
    — Доставить пассажира из from_location в to_location

Другим компонентом класса является office, представляющий диспетчерский офис службы такси.

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

  • такси имеет пассажиров (в противном случае комментарий для компонента take не имел бы смысла: кого следует доставить из одной точки в другую?). Класс должен иметь команду для посадки пассажиров и запрос, позволяющий выяснить текущее число пассажиров;
  • в любой момент такси имеет текущую позицию.

Где же находятся соответствующие свойства? Ответ можно найти, взглянув в начало объявления класса:

note
…
 class
  TAXI
inherit
  VEHICLE
feature
 ... Оставшаяся часть класса...

Класс TAXI наследует от VEHICLE ; и в самом деле, если посмотреть на класс VEHICLE, то можно найти команды load, для посадки пассажиров в транспортное средство, так же как и unload — для высадки, и запрос count, дающий текущее число пассажиров. Теперь, обратившись к началу объявления класса VEHICLE, вы увидите:

note
...
deferred class
  VEHICLE
inherit
  MOVING
feature
…Оставшаяся часть класса...

Класс VEHICLE наследует от класса MOVING, который описывает движущиеся объекты и имеет запрос position.

Классы VEHICLE и MOVING объявляются не просто как class, а как deferred class. Мы вскоре познакомимся детально с концепцией отложенного класса, указывающего на то, что не все его компоненты полностью заданы; реализация некоторых из них оставлена его потомкам — классам, наследующим от него.

Глядя, как эти три класса описывают типы объектов периода выполнения — такси, транспортные средства, движущиеся объекты, — мы понимаем, о чем говорит нам наследование. Оно устанавливает, что в системе Traffic любое такси может рассматриваться как транспортное средство, которое, в свою очередь, является движущимся объектом. В частности, все свойства класса MOVING применимы к целям типа VEHICLE и TAXI, и все свойства класса VEHICLE применимы к целям типа TAXI.

Термины наследования

Как обычно, помогает точная терминология.

Определения: наследник, родитель, (правильный) потомок и предок
Если B наследует от A ( В Eiffel A перечислено в предложении inherit класса B), то B наследник A, а A - родитель B. Потомками класса является сам класс и (рекурсивно) потомки его наследников. Сам класс не включается в число правильных потомков. Предок и правильный предок являются обращенными понятиями по отношению к потомкам.

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

В литературе иногда встречается термин "подкласс", означающий иногда наследника, иногда правильного потомка. Аналогично встречается и термин "суперкласс".

На рисунке ниже, иллюстрирующем наш пример, все классы являются потомками класса MOVING. Все классы — его правильные потомки, за исключением самого класса. Правильными предками класса TAXI являются классы VEHICLE и MOVING.

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

 Иерархия наследования

Рис. 1.1. Иерархия наследования

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

От вас не требуется вручную рисовать диаграмму, отображающую отношения между классами. Если классы скомпилированы, то инструментарий Diagram Tool EiffelStudio создаст диаграмму. Достаточно просто щелкнуть вкладку Diagram, и появится требуемая диаграмма:

Если класс не существует, но вы занимаетесь его проектированием, то также можно пользоваться этим инструментарием. Можно графически описать классы и задать связи (наследования и клиентские) между ними. В этом режиме будут автоматически сгенерированы соответствующие тексты классов.

Компоненты, приходящие от высших авторитетов

Мы можем теперь оценить новинку, вводимую наследованием. Понятие "компоненты класса" больше не означает только компоненты, заданные в классе, но и компоненты, наследуемые от родителя. Так, объявив объекты m: MOVING, v: VEHICLE, t: TAXI, мы можем помимо прочего писать:

v.load (...)
t.take (...)
v.count   — Выражение

Возможны и другие вызовы, такие как:

t.count
t.load
t.position
v.position

Здесь используются компоненты, наследуемые от родителя или, более точно, от правильных предков. Полезно различать "наследуемые" и "непосредственные" компоненты.

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

Компонент класса - это одно из двух:

  • наследуемый компонент, если это компонент одного из родителей класса;
  • непосредственный компонент, если он объявлен в классе и не наследуется. В этом случае говорят, что класс вводит компонент.

Заметьте, что определение включает рекурсию.

Отныне следует понимать, что класс А больше, чем то, что видимо в тексте класса. Компоненты класса означают не только компоненты, объявленные в тексте класса, но и те, что приходят от родителей, если таковые существуют.

Плоский облик

Как тогда получить полную картину? Плоским обликом класса называется искусственно сконструированная версия, которая включает все компоненты, непосредственные и наследуемые. Это не то, что вы пишете, создавая класс, но лишь облик, подобный контрактному облику, который, как мы видели, дает нам свободную от реализации версию класса. EiffelStudio создает этот взгляд на класс, этот облик, для чего достаточно выбрать класс и щелкнуть по кнопке "Flat view" :


Рис. 1.3.

Результат похож на обычный текст класса (с некоторой новой нотацией, которая позже прояснится). Отметьте появление нового вида комментариев, здесь для компонента LINKED_LIST :

 Плоский облик

Рис. 1.4. Плоский облик

Выделенных комментариев нет в исходном тексте, но они добавлены EiffelStudio, когда он создает плоский облик. Они указывают, что компонент наследуется, приходя от правильного предка CHAIN, и что его предусловие было определено в другом предке — LINEAR. Мы вскоре увидим, как контракты преобразуются в связи с наследованием.

Контрактный облик класса выводится (как объяснялось ранее, удаляется закрытая информация) не из исходного текста класса, а из его плоского облика. Для клиента класса обычно все равно, наследованы ли экспортируемые классом компоненты или введены самим классом. Интерфейсный облик класса позволяет ограничиться только непосредственными компонентами.

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

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