Проект "Люди и Машины"
Проектирование семейства классов, как правило, начинают с проектирования схемы, задающей иерархию и связи в семействе, определения прародителей, определяющих базовые свойства и поведение. Приведем в качестве примера семейство классов, объектами которого будут Люди и машины. Вот одна из возможных схем, определяющая классы этого семейства и отношения между ними.
Рис. 5.3. Схема семейства классов "Люди и Машины"
Мы не станем давать полную реализацию этой схемы. Для наших целей достаточно ограничиться ее частичной реализацией, где будут два родителя, имеющие общего потомка. У нас уже построен класс Личность, - пусть он и будет одним из родительских классов. Другим базовым родительским классом пусть будет класс Машина, задающий машины. Потомком этих двух родительских классов будет класс ВладелецМашины. Он наследует интерфейсы класса Личность и класса Машина. Поэтому объекты этого класса смогут входить и обрабатываться в разных группах, - в группе личностей и в группе машин. В нашем примере родительские классы не будут абстрактными. Напомним, что определенный в предыдущей лекции класс Личность является классом с событиями, так что его объекты могут реагировать на некоторые события, происходящие с ними. Мы не будем повторять здесь его описание. Класс Машина будет достаточно простым. Вот его определение:
Пример 5.4.
(html, txt)
У класса Машина имеется:
- три закрытых свойства, обладающие статусом Read only,
- два конструктора, - закрытый конструктор по умолчанию и открытый конструктор НоваяМашина,
- один типичный для классов открытый метод, задающий печать свойств - PrintDataCar
Интерфейс класса составляют два открытых метода, они и будут наследоваться классом - потомком. Класс - потомок ВладелецМашины значительно больше унаследует от своего другого родителя - класса Личность. Вот определение класса ВладелецМашины:
Пример 5.5.
(html, txt)
Прокомментируем этот довольно длинный текст. Вот на какие моменты следует обратить внимание:
- Для того, чтобы реализовать полноценное наследование свойств и поведения родителей, а не только наследование интерфейсов, используется встраивание, - в классе определены свойства Сам класса Личность и свойство ЕгоМашина класса Машина.
- В конструкторе по умолчанию Class_Initialize, вызываемом при создании объекта класса ВладелецМашины, объекты Сам и ЕгоМашина инициализируются, - будут вызваны их конструкторы по умолчанию класса Личность и класса Машина.
- При наследовании интерфейсов класса Личность почти для всех методов наследовалось поведение родительского класса.
Реализуется это достаточно просто - вызывается соответствующий метод объекта Сам. - Для метода SayWhoIs поведение переопределено. В данном случае при вызове метод учитывает специфику класса и сообщает некоторые данные, как о владельце, так и о его машине. Так что при вызове этого метода у просто личностей и у личностей, являющихся владельцами машин, результат будет различным.
- Поскольку родительский класс допускает события, то и у потомка при вызове методов зажигаются соответствующие события.
- Интерфейс класса Машина реализуется подобным образом. Полностью наследуется поведение родительского класса.
- Собственный интерфейс класса довольно прост. У объектов этого класса нет новых дополнительных свойств, - все спрятано в свойствах встроенных объектов Сам и ЕгоМашина. Интерфейс составляют два конструктора и метод, осуществляющий печать данных. Обратите внимание, один конструктор InitCarOwner позволяет сконструировать новый объект класса по терминальным данным, характеризующим личность и его машину. Второй конструктор ConnectOwnerAndCar в качестве параметров использует ранее созданные объекты классов Личность и Машина, соединяя эти два объекта в один объект класса ВладелецМашины.
На рисунке 5.4 отражен один из моментов проектирования класса. Можно видеть, что в окне кода список объектов содержит объекты Class, Личность и Машина. При выборе объекта Class правый раскрывающийся список покажет список стандартных событий класса, а при выборе объектов Личность и Машина будет раскрываться список наследуемых методов.
увеличить изображение
Рис. 5.4. Проектирование класса ВладелецМашины
Определение семейства классов дано и нам осталось продемонстрировать работу с различными объектами этих классов. Главное, что хотелось показать, это совместную работу с объектами разных классов. Начнем с программного текста:
Пример 5.6.
(html, txt)
Дадим теперь комментарии к этому тексту:
- В модуле Примеры вначале объявляются по три объекта классов Личность, Машина и ВладелецМашины.
В методах Люди, Cars, CarOwners происходит инициализация этих объектов. Некоторые из этих личностей уже встречались в предыдущих примерах, но появился Остап Бендер и его знаменитая машина. - Обратите внимание на создание объектов Owner класса ВладелецМашины, они создаются разными конструкторами.
- Основную работу выполняет метод Группа. Здесь создается массив элементов Group из трех объектов Friend и трех объектов Owner, принадлежащих разным классам. Тем не менее все эти объекты обрабатываются в едином цикле и для них вызывается метод SayWhoIs, который, как мы говорили ранее по-разному работает для объектов класса Личность и класса ВладелецМашины. Так что простой цикл по элементам Group демонстрирует реализацию полиморфизма.
- В методе Группа создана и продемонстрирована работа с еще одной группой элементов, - массивом Garage. Эту группу составляют объекты класса Машина и объекты класса ВладелецМашины. Обратите внимание, одни и те же объекты Owner входят в обе группы элементов и могут быть обработаны нужным образом.
- В процессе обработки массива Garage вызывается метод PrintDataCar, который унаследован объектами класса ВладелецМашины.
- В модуле Примеры приведена полиморфная функция PolyMorf(One As Личность), с формальным параметром родительского класса Личность. Она дважды вызывается в процедуре ЛюдиИМашины с фактическими параметрами разных классов.
- Процедура ЛюдиИМашины является процедурой, организующей всю работу, она поочередно запускает все перечисленные ранее процедуры. Сама она вызывается в обработчике события Click командной кнопки, встроенной в наш тестовый документ.
В процессе выполнения этой процедуры откроется серия диалоговых окон, где вначале будет сказано, кто такой Федотов и Катя, будет спрошено отчество Вегеры Юрия, затем опять будет рассказано о Федотове, Кате и Бендере, после чего появится информация о владельцах машин разных марок. На последнем этапе опять будет рассказано о Кате и владельце машины "Антилопа". Нам осталось привести результаты отладочной печати, появляющиеся при работе этой процедуры:
Пример 5.7.
(html, txt)
Как мы уже отмечали, родительский класс Личность реагирует на события. Это не мешает наследовать его интерфейсы, но не означает, что потомки будут наследовать события своего родителя. Чтобы убедиться, что зажигание событий не мешает работе потомков и не влияет на их работу, мы включили процедуру CallEvents. В процессе ее работы возникают события у объектов родительского класса и они правильно обрабатываются. Но, заметьте, после того как объект FriendOne был связан с объектом - потомком FriendOwner, при смене фамилии соответствующее событие уже не возникало, так что ничто не помешало Остапу Бендеру сменить свою фамилию. По ходу работы этой процедуры в появляющихся диалоговых окнах будет сообщено об отказе смены фамилии Федотову и появится поздравление Кате Павловой в связи с замужеством. Приведем результаты отладочной печати при работе этой процедуры:
21.05.39 22.03.79 Станислав Федотов родился 21.05.39 Катя Волконская родилась 22.03.79 Остап Воробьянинов родился 23.07.1910
Этот пример демонстрирует основные возможности работы с семейством классов, появившиеся в связи с введением механизма наследования интерфейсов.
При выборе объекта Class правый раскрывающийся список покажет список стандартных событий класса, а при выборе объектов Личность и Машина будет раскрываться список наследуемых методов.
увеличить изображение
Рис. 5.4. Проектирование класса ВладелецМашины
Определение семейства классов дано и нам осталось продемонстрировать работу с различными объектами этих классов. Главное, что хотелось показать, это совместную работу с объектами разных классов. Начнем с программного текста:
Option Explicit 'Модуль Примеры Public FriendOne As New Личность Public FriendTwo As New Личность Public FriendThree As New Личность Public carOne As New Машина Public carTwo As New Машина Public carThree As New Машина Public OwnerOne As New ВладелецМашины Public OwnerTwo As New ВладелецМашины Public OwnerThree As New ВладелецМашины Public FOne As New Личности
Public Sub Люди() 'Вызывается конструктор с параметрами 'и происходит знакомство с объектами FriendOne.InitPerson FN:="Станислав", LN:="Федотов", _ DoB:="21.05.39" FriendTwo.InitPerson FN:="Катя", LN:="Павлова", _ DoB:="22.03.79" FriendThree.InitPerson FN:="Остап", LN:="Бендер", DoB:="23.07.1910" FriendOne.PrintPerson FriendTwo.PrintPerson FriendOne.SayWhoIs FriendTwo.SayWhoIs 'Связывание с двойниками. 'Теперь объекты могут реагировать на события! FOne.Connect End Sub
Public Sub Cars() 'Вызывается конструктор с параметрами carOne.НоваяМашина "Антилопа", "12.12.12", "Неопределенный" carTwo.НоваяМашина "Москвич", "12.11.98", "Морская волна" carThree.НоваяМашина "Jeep", "23.05.97", "Orange" End Sub
Public Sub CarOwners() OwnerOne.ConnectOwnerAndCar FriendOne, carTwo OwnerTwo.ConnectOwnerAndCar FriendThree, carOne OwnerThree.InitCarOwner FN:="Юрий", LN:="Вегера", _ DoB:="21.08.34", Marka:="Газ69", DB:="20.01.76", Color:="Зеленый" OwnerOne.PrintOwnerData OwnerTwo.PrintOwnerData OwnerThree.PrintOwnerData End Sub Public Sub CallEvents() Dim DoB As Date 'Вызов методов приведет к возникновению событий! 'При замене фамилии возникнет событие ИзменениеФамилии 'Заметьте, не всегда фамилия будет изменена! FriendOne.ВашаФамилия = "Фидотов" FriendTwo.ВашаФамилия = "Волконская" 'При попытке узнать дату рождения 'может быть вызван обработчик события ДеньРождения.
DoB = FriendOne.ВашаДатаРождения Debug.Print DoB DoB = FriendTwo.ВашаДатаРождения Debug.Print DoB FriendOne.PrintPerson FriendTwo.PrintPerson
' События не наследуются Set FriendOne = OwnerTwo 'Нельзя связать теперь объект FriendOne с двойником 'FOne.Connect FriendOne.ВашаФамилия = "Воробьянинов" FriendOne.PrintPerson
End Sub Public Sub Группа() Const SizeGroup = 6 Const SizeGarage = 6 Dim i As Byte Dim Group(1 To SizeGroup) As Личность Dim Гараж(1 To SizeGarage) As Машина
Set Group(1) = FriendOne Set Group(2) = FriendTwo Set Group(3) = FriendThree Set Group(4) = OwnerOne Set Group(5) = OwnerTwo Set Group(6) = OwnerThree For i = 1 To SizeGroup Group(i).SayWhoIs Next i
Set Гараж(1) = carOne Set Гараж(2) = carTwo Set Гараж(3) = carThree Set Гараж(4) = OwnerOne Set Гараж(5) = OwnerTwo Set Гараж(6) = OwnerThree For i = 1 To SizeGarage Гараж(i).PrintDataCar Next i End Sub
Public Sub ЛюдиИМашины() Люди Cars CarOwners Группа PolyMorf FriendTwo PolyMorf OwnerTwo End Sub
Public Sub PolyMorf(One As Личность) One.SayWhoIs End Sub
Пример 5.6.
Дадим теперь комментарии к этому тексту:
- В модуле Примеры вначале объявляются по три объекта классов Личность, Машина и ВладелецМашины. В методах Люди, Cars, CarOwners происходит инициализация этих объектов. Некоторые из этих личностей уже встречались в предыдущих примерах, но появился Остап Бендер и его знаменитая машина.
- Обратите внимание на создание объектов Owner класса ВладелецМашины, они создаются разными конструкторами.
- Основную работу выполняет метод Группа. Здесь создается массив элементов Group из трех объектов Friend и трех объектов Owner, принадлежащих разным классам. Тем не менее все эти объекты обрабатываются в едином цикле и для них вызывается метод SayWhoIs, который, как мы говорили ранее по-разному работает для объектов класса Личность и класса ВладелецМашины. Так что простой цикл по элементам Group демонстрирует реализацию полиморфизма.
- В методе Группа создана и продемонстрирована работа с еще одной группой элементов, - массивом Garage.
Эту группу составляют объекты класса Машина и объекты класса ВладелецМашины. Обратите внимание, одни и те же объекты Owner входят в обе группы элементов и могут быть обработаны нужным образом. - В процессе обработки массива Garage вызывается метод PrintDataCar, который унаследован объектами класса ВладелецМашины.
- В модуле Примеры приведена полиморфная функция PolyMorf(One As Личность), с формальным параметром родительского класса Личность. Она дважды вызывается в процедуре ЛюдиИМашины с фактическими параметрами разных классов.
- Процедура ЛюдиИМашины является процедурой, организующей всю работу, она поочередно запускает все перечисленные ранее процедуры. Сама она вызывается в обработчике события Click командной кнопки, встроенной в наш тестовый документ.
В процессе выполнения этой процедуры откроется серия диалоговых окон, где вначале будет сказано, кто такой Федотов и Катя, будет спрошено отчество Вегеры Юрия, затем опять будет рассказано о Федотове, Кате и Бендере, после чего появится информация о владельцах машин разных марок. На последнем этапе опять будет рассказано о Кате и владельце машины "Антилопа". Нам осталось привести результаты отладочной печати, появляющиеся при работе этой процедуры:
Станислав Федотов родился 21.05.39 Катя Павлова родилась 22.03.79 Станислав Федотов родился 21.05.39 владеет машиной: Марка = Москвич ДатаВыпуска = 12.11.98 Цвет = Морская волна Остап Бендер родился 23.07.1910 владеет машиной: Марка = Антилопа ДатаВыпуска = 12.12.12 Цвет = Неопределенный Юрий Алексеевич Вегера родился 21.08.34 владеет машиной: Марка = Газ69 ДатаВыпуска = 20.01.76 Цвет = Зеленый Марка = Антилопа ДатаВыпуска = 12.12.12 Цвет = Неопределенный Марка = Москвич ДатаВыпуска = 12.11.98 Цвет = Морская волна Марка = Jeep ДатаВыпуска = 23.05.97 Цвет = Orange Марка = Москвич ДатаВыпуска = 12.11.98 Цвет = Морская волна Марка = Антилопа ДатаВыпуска = 12.12.12 Цвет = Неопределенный Марка = Газ69 ДатаВыпуска = 20.01.76 Цвет = Зеленый
Пример 5.7.
Как мы уже отмечали, родительский класс Личность реагирует на события.
Это не мешает наследовать его интерфейсы, но не означает, что потомки будут наследовать события своего родителя. Чтобы убедиться, что зажигание событий не мешает работе потомков и не влияет на их работу, мы включили процедуру CallEvents. В процессе ее работы возникают события у объектов родительского класса и они правильно обрабатываются. Но, заметьте, после того как объект FriendOne был связан с объектом - потомком FriendOwner, при смене фамилии соответствующее событие уже не возникало, так что ничто не помешало Остапу Бендеру сменить свою фамилию. По ходу работы этой процедуры в появляющихся диалоговых окнах будет сообщено об отказе смены фамилии Федотову и появится поздравление Кате Павловой в связи с замужеством. Приведем результаты отладочной печати при работе этой процедуры:
21.05.39 22.03.79 Станислав Федотов родился 21.05.39 Катя Волконская родилась 22.03.79 Остап Воробьянинов родился 23.07.1910
Этот пример демонстрирует основные возможности работы с семейством классов, появившиеся в связи с введением механизма наследования интерфейсов.