Полиморфизм
Принято считать, что полиморфизм – это выполнение разных действий процедурами или функциями с одинаковыми именами. Скажем, если для Автомобиля процедура Остановка это просто остановка, то для Автобуса его процедура Остановка – это еще и объявление по громкоговорителю названия остановки.
В VB полиморфизм осуществляется при помощи наследования и при помощи так называемых интерфейсов. При наследовании полиморфизм проявляется тогда, когда мы у наследника как-то изменяем (переопределяем) процедуру родителя.
Интерфейсы мы рассматривать не будем. Можно сказать, что интерфейс – это одна из масок, которую объект надевает на себя, чтобы с нами пообщаться.
Должен сказать, что Visual Basic версий 6.0 и более ранних не поддерживает настоящие полиморфизм и наследование. Впервые их поддержка осуществлена в Visual Basic.NET.
Пример полиморфизма через переопределение. Вернемся к нашему садовому товариществу. Будем работать над копией самой первой версии нашего проекта. В качестве подготовительных действий заменим функцией корявую процедуру вычисления периметра:
Public Class Участок
Public Владелец As String
Public Длина, Ширина As Integer
Public Высота_забора As Integer
Public Shared Расход_краски_на_кв_м As Integer
Public Function Периметр() As Integer
Return 2 * (Длина + Ширина)
End Function
Public Function Площадь_забора() As Integer
Return Периметр() * Высота_забора
End Function
Public Function Расход_краски_на_забор() As Integer
Return Расход_краски_на_кв_м * Площадь_забора()
End Function
End Class
Предположим теперь, что в товариществе появились владельцы, которые предпочитают сплошному забору штакетник, то есть забор с промежутками между досками. Ясно, что площадь такого забора меньше (предположим, в 2 раза). Создадим для таких участков новый класс – Участок_штакетник. Поскольку он будет отличаться от класса Участок только функцией Площадь_забора, то для сокращения кода сделаем его наследником класса Участок:
Public Class Участок_штакетник
Inherits Участок
Public Overrides
Function Площадь_забора() As Integer
Return 0.5 * Периметр() * Высота_забора
End Function
End Class
Мы видим, что у наследника определена функция с тем же именем, что и у родителя. И это очень хорошо, что с тем же именем, так как если бы мы придумали другое имя, то программисту извне перед вычислением площади забора приходилось бы каждый раз задумываться, штакетник ли на участке или сплошной забор. Если не верите, составьте программу вычисления суммарного расхода краски в товариществе для вариантов с одноименными и разноименными процедурами и сравните.
Однако мы привыкли, что при вызове методов наследника на самом деле вызываются родительские методы. В данной ситуации нам это не подойдет. Поэтому мы дописываем в заголовок функции наследника слово Overrides, что значит «пересиливает, переопределяет». Имеется в виду – пересиливает функцию родителя. С этой же целью мы дописываем в заголовок функции родителя слово Overridable, что значит «позволяет себя пересилить, переопределить»:
Public Overridable
Function Площадь_забора() As Integer
Return Периметр() * Высота_забора
End Function
Можете проверить все сказанное, использовав форму из 3 кнопок: одна создает обычный участок, другая – участок со штакетником, третья печатает расход краски на каждом участке.
MyBase и MyClass. Итак, у нас теперь две функции Площадь_забора, одна у родителя, другая – у наследника. Теперь, когда бы мы в коде наследника ни обратились к функции Площадь_забора, вызовется функция наследника, а не родителя. А если нам захочется обратиться из кода наследника именно к родительской функции? Вы спрашиваете, зачем это нужно? Вот конкретный пример. Замечаем, что площадь забора из штакетника вдвое меньше, чем у обычного. В связи с этим нам кажется проще вычислить площадь обычного забора, а затем поделить ее пополам. Переписываем функцию в классе Участок_штакетник:
Public Overrides Function Площадь_забора() As Integer
Return 0.5 * MyBase.Площадь_забора
End Function
Слово MyBase с точкой указывает, что имеется в виду именно родительская функция.
Когда возникает потребность в коде наследника указать, что имеется в виду именно компонент наследника, а не родителя или кого-то другого, то вместо MyBase употребляют слово MyClass.
Shadows. Когда большой проект, использующий наследование, разрабатывает группа программистов, часто случается, что через некоторое время после того, как проект уже сдан в эксплуатацию и широко используется, выявляются некоторые недостатки проекта, требующие исправления. При этом какие-то родительские классы подвергаются доработке. В результате доработки в коде родительских классов могут возникнуть новые программные элементы (переменные, процедуры и др.), случайно получившие такие же имена, как совсем другие программные элементы ничего не подозревающих наследников. Например, в родительском классе появляется процедура А1, в то время как в наследнике уже давно имеется переменная А1. VB не любит такие ситуации, считает их конфликтными и рекомендует программистам классов-наследников «на всякий пожарный» явно указывать, что их программный элемент пересиливает (затеняет) любой могущий объявиться программный элемент родителя. Делается это в классе-наследнике так:
Public Shadows А1 As String
Слово Shadows переводится с английского, как «затеняет».
Данная ситуация похожа на ситуацию с переопределением, но это не одно и то же. На различиях я не останавливаюсь.