ПОНЯТНО О Visual Basic NET (том 3)

Полиморфизм


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

В 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 переводится с английского, как «затеняет».

Данная ситуация похожа на ситуацию с переопределением, но это не одно и то же. На различиях я не останавливаюсь.


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