Основы наследования

Хотя наследование не является панацеей ООП и во многих ситуациях лучше воспользоваться интерфейсами, не стоит полагать, что без наследования можно как-нибудь обойтись. Наследование — замечательное средство, способное сэкономить немало времени и сил... если им правильно пользоваться. Критерий правильного использования прост: не используйте наследование, если у вас нет абсолютной уверенности в существовании логической связи типа «является частным случаем».
Класс А объявляется производным от класса В только в том случае, если вы точно знаете, что сейчас и в сколь угодно отдаленном будущем объект А может использоваться вместо объекта В и это не вызовет никаких проблем.
(Помните пример из главы 4? Оформляя внештатного работника по правилам для обычных сотрудников, вы наживете неприятности с налоговой инспекцией. Класс Contractor не должен объявляться производным от класса Employee даже при том, что "они обладают рядом сходных черт.)
Ниже этот фундаментальный принцип приведен в слегка измененном, более абстрактном виде, ориентированном на практическое программирование.
Экземпляр класса А, производного от класса В, должен нормально работать в каждом фрагменте программы, которому в качестве параметра передается экземпляр базового типа.
Предположим, у вас имеются функция UseIt(bTh1 ng As В) и объект aThi ng, который является экземпляром производного класса А. Следующий вызов должен нормально работать:


Uselt(aThing)

Если все эти рассуждения выглядят слишком абстрактными, ниже приведен вымышленный (и надеемся, забавный) пример. Предположим, вы размышляете над тем, от какого класса следует объявить производным класс ManagerOf Programmers — от Manager или от Programmer? Всем известно, что менеджеры носят аккуратные прически, поэтому класс Manager должен содержать метод SetHalrStyle. А теперь закройте глаза и представьте типичного программиста, которого вдруг назначили управлять другими программистами. Захочет ли он менять свой имидж? Можете ли вы уверенно заявить, что вызов вида

tom.SetHairStyle("sharp razor cut")


всегда имеет смысл? Конечно, среди программистов иногда встречаются экземпляры, которые заботятся о своей прическе, но обо всех программистах этого никак не скажешь. Мораль: класс ManagerOf Programmers должен быть производным от класса Programmer, а не от Manager.

Некоторые языки позволяют объявить класс ManagerOfProgrammers производным как от Manager, так и от Programmer. Теоретически такая возможность выглядит вполне логично и привлекательно, но на практике языки с множественным наследованием (так это называется по-научному) порождают массу проблем. Вместо множественного наследования в VB .NET используется реализация нескольких интерфейсов. Как вы вскоре увидите, этот вариант значительно проще и нагляднее, нежели классическое множественное наследование.

В сущности, при программировании в VB .NET вам никак не удастся скрыться от наследования. Даже если вы привыкли к интерфейсному стилю программирования VB5 и VB6 и считаете, что для ваших задач достаточно интерфейсов вкупе с включением и делегированием, ограничиться одними интерфейсами в VB .NET невозможно. Дело в том, что без явного использования наследования вы не сможете пользоваться .NET Framework. Наследование заложено в основу любого графического приложения .NET, а также многих встроенных классов коллекций — даже работа с объектом FolderBrowser связана с наследованием!

Применение наследования при построении графических приложений в рекламной литературе VB .NET иногда именуется визуальным наследованием. Не обращайте внимания — это самое обычное наследование. То, что ваш класс является производным от Windows.Forms.Form, сути дела не меняет.

Более того, сам подход к применению наследования в .NET Framework как нельзя лучше доказывает, что наследование в объектно-ориентированном программировании не должно полностью вытесняться интерфейсами. Подход, примененный в .NET Framework, вполне может применяться и в ваших собственных проектах.

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

 

Знакомство с наследованием

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

Следующий пример наглядно показывает, как это происходит. Допустим, у нас имеется компания с передовой политикой в области материального стимулирования. Каждый раз, когда заработная плата всех служащих компании повышается на 5%, для программистов прибавка составляет 6%. Вам поручено разработать систему учета кадров для этой компании. Вы решаете определить класс Programmer, производный от Employee, и переопределить метод RaiseSal агу в классе Programmer, чтобы отразить автоматическую (и вполне заслуженную!) надбавку.

Итак, приступим к программированию цепочки наследования Employee—>Programmer. Допустим, у нас уже имеется класс Publiс Employee, который входит в решение или включается в него командой Project > References. В этом случае начало кода класса Programmer будет выглядеть так (ключевая строка выделена жирным шрифтом):

Public Class Programmer

Inherits Employee

End Class

Ключевое слово Inherits должно находиться в первой не пустой и не содержащей комментария строке после имени производного класса (кстати, IntelliSense подскажет имена возможных базовых классов). Учтите, что производный класс не может объявляться с модификатором Publ i с, если базовый класс объявлялся с модификатором Friend или Private. Это связано с тем, что модификатор уровня доступа в производном классе не может быть менее ограничивающим, чем модификатор базового класса. С другой стороны, он может устанавливать более жесткие ограничения, поэтому от базового класса с уровнем доступа Publ i с можно объявить производный класс с уровнем Friend.

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

Public Sub New(ByVal theName As String, ByVal curSalary As Decimal)

MyBase.NewCName.curSalary)

End Sub

Ключевая строка, выделенная жирным шрифтом, вызывает конструктор базового класса Empl oyee и инициализирует его поля. Если вы забудете вызвать MyBase. New в том случае, когда конструктор базового класса вызывается с аргументами, VB .NET выдает сообщение об ошибке следующего вида:

C:\vb net book \chapter 5 \Examplel \Examplel \Modulel.vb(55):

'Examplel.Programmer'.the base class of 'Examplel.Employee'.

does not have an accessible constructor that can be called with

no arguments. Therefore.the first statement of this constructor

must be a call to a constructor of the base class via 'MyBase.New'

or another constructor of this class via 'MyClass.New' or 'Me.New'.

Хорошо бы, чтобы все сообщения об ошибках были настолько содержательными и понятными. Компилятор напоминает о том, что при отсутствии у базового класса безаргументного конструктора производный класс должен содержать хотя бы один вызов MyBase. New. После включения в программу вызова MyBase. New возникает очень интересный вопрос: как обращаться к полям базового класса? Следующее правило на первый взгляд может вас удивить:

Производный класс не обладает привилегированным доступом к полям базовою класса.

Из этого правила следует, что производный класс Programmer не получает доступа к закрытым полям базового класса Employee. Предположим, заработная плата хранится в закрытом поле базового класса с именем m_Sal ary и вы пытаетесь включить в код метода RaiseSalary класса Programmer следующий фрагмент:

Public Sub New(ByVal theName As String. ByVal curSalary As Decimal)

MyBase.New(theName. curSalary)

MyBase.m_salary = 1.2 * curSalary End Sub

Компилятор выдает сообщение об ошибке:

'Examplel.Employee.m_Salary'is Private.and is not accessible

in this context.

В повседневной жизни существует хорошая аналогия — родители устанавливают правила поведения для детей, а не наоборот.

Что же делать? Если вы хотите, чтобы производный класс получил доступ к некоторым возможностям базового класса, об этом должен позаботиться сам базовый класс. В следующем разделе будет показано, как это делается.