Наследование
Кроме инкапсуляции у классов есть еще две замечательные черты: наследование и полиморфизм. Они могут быть важны в тех проектах, где имеются не один-два класса, а несколько. Понятие о наследовании я сначала дам на уровне аналогии.
Аналогия. Вообразим, что мы создали простенький класс Автомобиль, наделив его всего лишь двигателем, рулем и 4 колесами. И никаких подробностей. Полюбовавшись работой получившейся самоходной тележки, мы решили развивать проект дальше и создать еще три класса, более богатых и подробных: Амфибия, Грузовик и Автобус. У каждого из новых классов, как и у Автомобиля, есть двигатель, руль и 4 колеса, но вдобавок к ним и много подробностей, например, у Амфибии гребной винт, у Грузовика – кузов, у Автобуса – пассажирский салон.
Как создавать код каждого из этих трех классов? В нашем случае можно было бы просто скопировать в каждый из них весь код Автомобиля, добавив затем для каждого класса свои переменные, процедуры и функции. Но есть более удобный и «правильный» способ – это наследование. Мы просто объявляем, что новый класс Грузовик является наследником класса Автомобиль. При этом Грузовик неявно (невидимо) приобретает весь код своего родителя – Автомобиля. Тем самым, ничего не записав в код Грузовика, мы уже можем пользоваться им как Автомобилем. А чтобы снабдить Грузовик дополнительными чертами, мы пишем ему новые процедуры и функции. Аналогично поступаем с Амфибией и Автобусом.
Самое интересное то, что если мы впоследствии изменяем что-то в коде родителя (Автомобиль), то это изменение тут же сказывается на наследниках. Например, если мы в Автомобиле заменили двигатель внутреннего сгорания на электрический, то эта замена немедленно произойдет и в Амфибии, и в Грузовике, и в Автобусе. Это очень ценное и удобное качество. При отсутствии наследования нам пришлось бы делать замену двигателя в каждом классе.
У детей могут быть свои дети. Так, у Грузовика могут быть наследники Самосвал, Пикап и др. Все они наследуют у папаши-Грузовика кузов, а у дедушки-Автомобиля – двигатель, руль и 4 колеса. В общем, гены передаются самым отдаленным потомкам.
Рассмотрим пример. Пусть имеется класс clsПрямоугольник. Его дело – предоставлять внешнему пользователю возможность вычислять по длине и ширине прямоугольника его площадь и периметр:
Public Class clsПрямоугольник
Public Длина As Integer
Public Ширина As Integer
Public Function Площадь() As Integer
Return Длина * Ширина
End Function
Public Function Периметр() As Integer
Return 2 * Длина + 2 * Ширина
End Function
End Class
Предположим, впоследствии у нас появилась необходимость по длине, ширине и высоте параллелепипеда вычислять его объем. Мы могли бы просто дополнить наш класс clsПрямоугольник следующим кодом:
Public Высота As Integer
Public Function Объем() As Integer
Return Площадь() * Высота
End Function
и это решило бы дело. Но нам не хочется расширять класс clsПрямоугольник и придавать ему несвойственные его названию черты параллелепипеда. К тому же мы пользуемся им слишком часто и не хотим, чтобы он, растолстев, напрягал ресурсы компьютера.
Поэтому создаем новый класс – clsПараллелепипед:
Public Class clsПараллелепипед
Inherits
clsПрямоугольник
Public Высота As Integer
Public Function Объем() As Integer
Return Площадь() * Высота
End Function
End Class
Как видите, класс получился очень короткий, но делает все что нужно. Это заслуга наследования. Строка
Inherits
clsПрямоугольник
говорит о том, что он наследует все компоненты класса clsПрямоугольник. Слово Inherits переводится «наследует». В простейшем случае наследование выглядит так, как если бы мы скопировали код родителя в тело наследника.
Обратите внимание, что не все, что есть у родителя, нужно наследнику. Я имею в виду периметр. Ну что ж, видимо, идеальных родителей не бывает.
Продолжаем пример. У родителя может быть много детей. Создадим еще один класс – clsКоробка, дело которого – вычислять полную площадь поверхности параллелепипеда. Вот он:
Public Class clsКоробка
Inherits
clsПрямоугольник
Public Высота As Integer
Public Function Площадь_поверхности() As Integer
Return 2 * Площадь() + Периметр() * Высота
End Function
End Class
Вы видите, что каждый из наследников имеет 3 поля: длину, ширину и высоту – и умеет вычислять площадь и периметр. Проверим работу классов, нажав кнопку на форме:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Прямоугольник As New clsПрямоугольник
Прямоугольник.Длина = 10
Прямоугольник.Ширина = 5
Debug.WriteLine(Прямоугольник.Площадь)
Debug.WriteLine(Прямоугольник.Периметр)
Dim Параллелепипед As New clsПараллелепипед
Параллелепипед.Длина = 4
Параллелепипед.Ширина = 3
Параллелепипед.Высота = 2
Debug.WriteLine(Параллелепипед.Площадь)
Debug.WriteLine(Параллелепипед.Периметр)
Debug.WriteLine(Параллелепипед.Объем)
Dim Коробка As New clsКоробка
Коробка.Длина = 3
Коробка.Ширина = 2
Коробка.Высота = 100
Debug.WriteLine(Коробка.Площадь)
Debug.WriteLine(Коробка.Периметр)
Debug.WriteLine(Коробка.Площадь_поверхности)
End Sub
Вот что будет напечатано:
50
30
12
14
24
6
10
1012
Обратите внимание, что наследуются переменные величины в классах, а не их конкретные значения в объектах. Так, значение длины прямоугольника никак не влияет на значение длины коробки.
Внуки. У детей, как я уже говорил, могут быть свои дети. Так, у класса clsКоробка могут быть наследники: Гараж, Чемодан и др. Все они наследуют у родителя – класса clsКоробка – высоту и площадь поверхности, а у дедушки – класса clsПрямоугольник – длину, ширину, площадь и периметр. Факт наследования объявляется аналогично:
Public Class clsГараж
Inherits
clsКоробка
Область видимости Protected. Если какой-нибудь компонент родительского класса объявлен Private, то у наследника не будет доступа к этому компоненту. Когда такой доступ нужен, компонент рекомендую объявлять Protected. В этом случае он будет доступен наследникам, но только им. Пример проекта:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Наследник As New clsНаследник
Debug.WriteLine(Наследник.Фн)
End Sub
Public Class clsРодитель
Protected А As Integer = 100
Private B As Integer = 200
Protected Function Ф() As Integer
Return B + 1
End Function
End Class
Public Class clsНаследник
Inherits clsРодитель
Public Function Фн() As Integer
Return Ф + А
End Function
End Class
Проект напечатает
301
Если бы мы объявили так:
Private А
As Integer = 100
Private Function Ф() As Integer
то в строке
Return Ф + А
были бы подчеркнуты и Ф, и А, поскольку ни переменная, ни функция были бы недоступны.