BiVANT   Книги   К началу главы

В.А. Биллиг
VBA в Office 2000
Офисное программирование
"Русская редакция", 1999 г.

Глава 5(4)

Проект «Люди и Машины»

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

  f5_3.gif (2673 bytes)

Рис. 5_3 Схема семейства классов «Люди и Машины»

Мы не станем давать полную реализацию этой схемы. Для наших целей достаточно ограничиться ее частичной реализацией, где будут два родителя, имеющие общего потомка. У нас уже построен класс Личность, ¾ пусть он и будет одним из родительских классов. Другим базовым родительским классом пусть будет класс Машина, задающий машины. Потомком этих двух родительских классов будет класс ВладелецМашины. Он наследует интерфейсы класса Личность и класса Машина. Поэтому объекты этого класса смогут входить и обрабатываться в разных группах, ¾ в группе личностей и в группе машин. В нашем примере родительские классы не будут абстрактными. Напомним, что определенный в предыдущей главе класс Личность является классом с событиями, так что его объекты могут реагировать на некоторые события, происходящие с ними. Мы не будем повторять здесь его описание. Класс Машина будет достаточно простым. Вот его определение:

Option Explicit
'Класс Машина
'Свойства класса
Private Марка As String
Private ДатаВыпуска As Date
Private Цвет As String

'Конструкторы класса

Private Sub Class_Initialize()
    Марка = "Форд"
    ДатаВыпуска = "20.07.1925"
    Цвет = "Вишневый"
End Sub

Public Sub НоваяМашина(M As String, D As Date, C As String)
    Марка = M
    ДатаВыпуска = D
    Цвет = C
End Sub

'Методы класса
Public Sub PrintDataCar()
    Debug.Print "Марка = ", Марка
    Debug.Print "ДатаВыпуска = ", ДатаВыпуска
    Debug.Print "Цвет = ", Цвет
   
End Sub

Public Property Get МаркаМашины() As String
    МаркаМашины = Марка
End Property

Public Property Get ЦветМашины() As String
    ЦветМашины = Цвет
End Property

Public Property Get ДатаВыпускаМашины() As Date
    ДатаВыпускаМашины = ДатаВыпуска
End Property
У класса Машина имеется:

·   три закрытых свойства, обладающие статусом Read only,

·   два конструктора, ¾ закрытый конструктор по умолчанию и открытый конструктор НоваяМашина,

·   один типичный для классов открытый метод, задающий печать свойств ¾ PrintDataCar

Интерфейс класса составляют два открытых метода, они и будут наследоваться классом - потомком. Класс - потомок ВладелецМашины значительно больше унаследует от своего другого родителя - класса Личность. Вот определение класса ВладелецМашины:

Option Explicit
'Класс ВладелецМашины
'Наследует интерфейсы классов Личность и Машина
Implements Машина
Implements Личность

'Свойства класса
Private Сам As Личность
Private ЕгоМашина As Машина

Private Sub Class_Initialize()
 Set Сам = New Личность
 Set ЕгоМашина = New Машина
End Sub

'Реализация интерфейсов класса Личность
Private Sub Личность_CopyPerson(You As Личность)
    Сам.CopyPerson (You)
End Sub

Private Sub Личность_InitPerson(ByVal FN As String, ByVal LN As String, ByVal DoB As Date)
 'Инициализация личности
    Сам.InitPerson FN, LN, DoB
End Sub

Private Sub Личность_PrintPerson()
 'Печать в отладочном окне Immediate
    Сам.PrintPerson
End Sub

Private Sub Личность_SayWhoIs()
' Вывод сообщения о поле и возрасте владельца машины
     Dim StrMsg As String
     StrMsg = "Думаю, Владелец машины марки: " & _
    ЕгоМашина.МаркаМашины & " это - "
     If Сам.WhoIs Then
       If Year(Сам.ВашаДатаРождения) > 1967 Then
         StrMsg = StrMsg & "молодая девушка!"
      
Else: StrMsg = StrMsg & "женщина!"
       End If
     Else
       If Year(
Сам.ВашаДатаРождения) > 1967 Then
         StrMsg = StrMsg & "
молодой человек!"
       Else: StrMsg = StrMsg & "
мужчина!"
       End If
     End If
     MsgBox (StrMsg)
End Sub

Private Function
Личность_WhoIs() As Boolean
   
Сам.WhoIs
End Function

Private Property Let
Личность_ВашаДатаРождения(ByVal NewValue As Date)
   
Сам.ВашаДатаРождения = NewValue
End Property

Private Property Get
Личность_ВашаДатаРождения() As Date
    '
Зажигает событие ДеньРождения
     '
в зависимости от значения текущей даты
   
Личность_ВашаДатаРождения = Сам.ВашаДатаРождения
End Property

Private Property Let
Личность_ВашаФамилия(ByVal NewValue As String)
  '
Зажигает событие ИзменениеФамилии
   
Сам.ВашаФамилия = NewValue
End Property

Private Property Get
Личность_ВашаФамилия() As String
   
Личность_ВашаФамилия = Сам.ВашаФамилия
End Property

Private Property Let
Личность_ВашеИмя(ByVal NewValue As String)
   
Сам.ВашеИмя = NewValue
End Property

Private Property Get
Личность_ВашеИмя() As String
   
Личность_ВашеИмя = Сам.ВашеИмя
End Property

Private Property Let
Личность_ВашеОтчество(ByVal NewValue As String)
 
Сам.ВашеОтчество = NewValue
End Property

Private Property Get
Личность_ВашеОтчество() As String
   
Личность_ВашеОтчество = Сам.ВашеОтчество
End Property

'
Реализация интерфейсов класса Машина
Private Property Get
Машина_ДатаВыпускаМашины() As Date
 
Машина_ДатаВыпускаМашины = ЕгоМашина.ДатаВыпускаМашины
End Property

Private Property Get
Машина_МаркаМашины() As String
   
Машина_МаркаМашины = ЕгоМашина.МаркаМашины
End Property

Private Property Get
Машина_ЦветМашины() As String
   
Машина_ЦветМашины = ЕгоМашина.ЦветМашины
End Property

Private Sub
Машина_PrintDataCar()
   
ЕгоМашина.PrintDataCar
 End Sub

Private Sub
Машина_НоваяМашина(M As String, D As Date, C As String)
 
ЕгоМашина.НоваяМашина M, D, C
End Sub

'
Собственный интерфейс класса ВладелецМашины
'Public
методы - интерфейс Владельца машины
Public Sub InitCarOwner(FN As String, LN As String, DoB As Date, _
     Marka As String, DB As Date, Color As String)
    '
Инициализация данных о хозяине и его машине
   
Личность_InitPerson FN, LN, DoB
   
Машина_НоваяМашина Marka, DB, Color
End Sub

 Public Sub ConnectOwnerAndCar(pers As
Личность, car As Машина)
    '
соединяет данные о хозяине и его новой машине
   
Сам.CopyPerson pers
   
Машина_НоваяМашина car.МаркаМашины, car.ДатаВыпускаМашины, _
       car.
ЦветМашины
End Sub

Public Sub PrintOwnerData()
   
Личность_PrintPerson
    Debug.Print "
владеет машиной: "
   
Машина_PrintDataCar
End Sub

Прокомментируем этот довольно длинный текст. Вот на какие моменты следует обратить внимание:

1.     Для того, чтобы реализовать полноценное наследование свойств и поведения родителей, а не только наследование интерфейсов, используется встраивание, ¾ в классе определены свойства Сам класса Личность и свойство ЕгоМашина класса Машина.

2.     В конструкторе по умолчанию Class_Initialize, вызываемом при создании объекта класса ВладелецМашины, объекты Сам и ЕгоМашина инициализируются, ¾ будут вызваны их конструкторы по умолчанию класса Личность и класса Машина.

3.     При наследовании интерфейсов класса Личность почти для всех методов наследовалось поведение родительского класса. Реализуется это достаточно просто ¾ вызывается соответствующий метод объекта Сам.

4.     Для метода SayWhoIs поведение переопределено. В данном случае при вызове метод учитывает специфику класса и сообщает некоторые данные, как о владельце, так и о его машине. Так что при вызове этого метода у просто личностей и у личностей, являющихся владельцами машин, результат будет различным.

5.     Поскольку родительский класс допускает события, то и у потомка при вызове методов зажигаются соответствующие события.

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

7.     Собственный интерфейс класса довольно прост. У объектов этого класса нет новых дополнительных свойств, - все спрятано в свойствах встроенных объектов Сам и ЕгоМашина. Интерфейс составляют два конструктора и метод, осуществляющий печать данных. Обратите внимание, один конструктор InitCarOwner позволяет сконструировать новый объект класса по терминальным данным, характеризующим личность и его машину. Второй конструктор ConnectOwnerAndCar в качестве параметров использует ранее созданные объекты классов Личность и Машина, соединяя эти два объекта в один объект класса ВладелецМашины.

На рисунке F5_2 отражен один из моментов проектирования класса. Можно видеть, что в окне кода список объектов содержит объекты Class, Личность и Машина. При выборе объекта Class правый раскрывающийся список покажет список стандартных событий класса, а при выборе объектов Личность и Машина будет раскрываться список наследуемых методов.

  f5_4.gif (75737 bytes)

Рис. 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

Дадим теперь комментарии к этому тексту:

1.     В модуле Примеры вначале объявляются по три объекта классов Личность, Машина и ВладелецМашины. В методах Люди, Cars, CarOwners происходит инициализация этих объектов. Некоторые из этих личностей уже встречались в предыдущих примерах, но появился Остап Бендер и его знаменитая машина.

2.     Обратите внимание на создание объектов Owner класса ВладелецМашины, они создаются разными конструкторами.

3.     Основную работу выполняет метод Группа. Здесь создается массив элементов Group из трех объектов Friend и трех объектов Owner, принадлежащих разным классам. Тем не менее все эти объекты обрабатываются в едином цикле и для них вызывается метод SayWhoIs, который, как мы говорили ранее по-разному работает для объектов класса Личность и класса ВладелецМашины. Так что простой цикл по элементам Group демонстрирует реализацию полиморфизма.

4.     В методе Группа создана и продемонстрирована работа с еще одной группой элементов,  ¾ массивом Garage. Эту группу составляют объекты класса Машина и объекты класса ВладелецМашины. Обратите внимание, одни и те же объекты Owner входят в обе группы элементов и могут быть обработаны нужным образом.

5.     В процессе обработки массива Garage вызывается метод PrintDataCar, который унаследован объектами класса ВладелецМашины.

6.     В модуле Примеры приведена полиморфная функция PolyMorf(One As Личность), с формальным параметром родительского класса Личность. Она дважды вызывается в процедуре ЛюдиИМашины с фактическими параметрами разных классов.

7.     Процедура ЛюдиИМашины является процедурой, организующей всю работу, она поочередно запускает все перечисленные ранее процедуры. Сама она вызывается в обработчике события 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
Цвет =      Зеленый

Как мы уже отмечали, родительский класс Личность реагирует на события. Это не мешает наследовать его интерфейсы, но не означает, что потомки будут наследовать события своего родителя. Чтобы убедиться, что зажигание событий не мешает работе потомков и не влияет на их работу, мы включили процедуру CallEvents. В процессе ее работы возникают события у объектов родительского класса и они правильно обрабатываются. Но, заметьте, после того как объект FriendOne был связан с объектом – потомком FriendOwner, при смене фамилии соответствующее событие уже не возникало, так что ничто не помешало Остапу Бендеру сменить свою фамилию. По ходу работы этой процедуры в появляющихся диалоговых окнах будет сообщено об отказе смены фамилии Федотову и появится поздравление Кате Павловой в связи с замужеством. Приведем результаты отладочной печати при работе этой процедуры:

21.05.39
22.03.79
Станислав             Федотов     родился     21.05.39
Катя                Волконская    родилась   22.03.79
Остап               Воробьянинов  родился     23.07.1910

 

Этот пример демонстрирует основные возможности работы с семейством классов, появившиеся в связи с введением механизма наследования интерфейсов.

  Назад             Вперед  

 

Hosted by uCoz