FAQ по Visual Basic

Содержание / Начальные сведения о VB Поиск | Далее

§ 3.3. Что такое классы и с чем их едят?

Тема эта столь обширна и затрагивает так много областей программирования, что ей место не в кратеньком FAQ'е, а в обстоятельной книжке. Но я попробую совместить несовместимое, и надеюсь, гуру не будут в обиде, если я опущу частности и то, что мне показалось неважным.

Что такое класс? Это совокупность методов, свойств и событий, которые образуют целостный объект.

А теперь попроще. Объект — наручные часы. Методы предназначены для установки времени на часах, установки будильника и т. д. Свойства, в нашем случае, определяют цвет корпуса, вид индикатора и т. д. Звук будильника является событием и позволяет провести определенные действия (например, снять молоко с плиты) в заданное время. Чтобы получить доступ к свойствам, методам и событиям объекта (часов), необходимо создать указатель на него, или инстанс. Если класс называется Clock, то создание экземпляра класса (инстанса) будет выглядеть так:

Dim clk As Clock
Set clk = New Clock

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

' вызовем метод установки времени
clk.SetTime "13:56"
' а теперь изменим свойство "Цвет корпуса"
clk.Color = "Gold"
' или увеличим свойство "Цена" на 1000 баксов ;-)
clk.Price = clk.Price + 1000

А теперь самое сложное: создание классов. Начнем с азов.

  1. Добавление модуля класса в проект.
  2. Добавить модуль класса (часто его называют классовым модулем) можно посредством меню Project — Add Class Module или же кнопки “Insert Object” на панели инструментов. Далее, назовите ваш новоиспеченный класс. Поскольку через данную статью красной нитью проходит пример с часами, то так и назовем первый классовый модуль: Clock.

  3. Область видимости процедур, свойств, переменных: Private, Public.
  4. Очень часто, создавая переменные или процедуры, приходится решать вопрос: можно ли будет их использовать вне модуля, или они необходимы только самому модулю. В первом случае такие процедуры или переменные объявляются с префиксом Public, а во втором случае — с префиксом Private. Префикс Public является умолчательным, и хотя его можно опускать, я рекомендую этого не делать. Добавьте в проект простой, не классовый, модуль и наберите в нем:

    ' эта переменная видна снаружи модуля
    Public MyFirstVar As Integer
    ' а эта переменная является приватной
    ' и не видна вне модуля
    Private MySecondVar as Integer
    

    Создайте в том же модуле процедуру CheckVar и обратитесь из нее к этим двум переменным:

    Public Sub CheckVar()
        MyFirstVar = 5
        MySecondVar = 7
    End Sub
    

    Это удается без труда. А теперь обратимся из модуля формы (он по умолчанию уже входит в проект) к созданным переменным. Как видим, вторая переменная не существует для всего мира ;-) и доступна только в модуле, ее породившем. Попробуйте самостоятельно создать публичную и приватную процедуры и обратится к ним из модуля и из модуля формы.

  5. Методы.
  6. Методы — это уже знакомые вам процедуры (sub) и функции (function). Метод может иметь аргументы, а может не иметь их. Если метод построен на основе функции, то он способен возвращать значения. Создадим метод установки времени на наших часах и метод, возвращающий информацию о состоянии батарейки питания. Для этого в модуле класса напишем:

    ' переменная для хранения времени
    Private m_Time As String
    
    ' публичный метод установки времени
    Public Sub SetTime(a_Time As String)
        ' запомним значение
        m_Time = a_Time
    
        ' произведем какие-то действия
        . . .
    
    End Sub
    
    ' публичный метод, возвращающий напряжение
    ' батарейки питания
    Public Function GetPowerState () As Single
        ' определим напряжение питания батарейки
        . . .
        ' просто эмуляция работы некоего кода
        BatteryVoltage = 1.48
    
        ' возвратим значение
        GetPowerState = BatteryVoltage
    
    End Function
    

    Создав инстанс класса (как было показано в начале статьи), обращаемся к новоиспеченным методам:

    Dim sTime As String
    
    ' установим время
    sTime = InputBox ("Введите текущее время:", _
        "Часики", "18:16")
    clk.SetTime sTime
    
    ' определим состояние батарей
    ' в окне будет напечатано 1,48
    MsgBox clk.GetPowerState()
    
  7. Свойства.
  8. Условно говоря, свойство очень условно можно представить как пара состоящая, из функции и процедуры с одним и тем же именем. При обращении к свойству вызывается функция, при изменении — процедура. Если отойти от условностей, то для создания свойств существует ключевое слово Property с суффиксом Get для получения значения и с суффиксом Let или Set (для объектов) для установки значения. Вообще, свойство чем-то похоже на объявленную публичную переменную. С тем лишь отличием, что изменение значения такой переменной нельзя отследить, как и нельзя отследить обращения к этой переменной. Свойства же позволяют это реализовать.

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

    Public Price As Single
    

    Тогда обращение к этой переменной извне выглядит так:

    ' установить стоимость часов
    clk.Price = 500
    ' выдать стоимость часов
    MsgBox clk.Price
    

    Поставим задачу. Пользователь не должен иметь возможности установить цену ниже 150 и выше 800 долларов. Переменная с этим справиться неспособна. Берем на помощь процедуры и функции:

    Private m_Price As Single
    
    ' процедура получения цены
    Public Function GetPrice() As Single
        GetPrice = m_Price
    End Function
    
    ' процедура установки цены
    Public Sub SetPrice(a_Price As Single)
        If a_Price <= 800 And a_Price >= 150 Then
            m_Price = a_Price
        End If
    End Sub
    

    Можно так написать? Можно. Все будет работать, но возникает ощущение некоей громоздкости, нерациональности:

    ' установить стоимость часов
    clk.SetPrice 450
    ' выдать стоимость часов
    MsgBox clk.GetPrice
    

    И тут на помощь приходят свойства:

    Private m_Price As Single
    
    ' свойство получения цены
    Public Property Get Price() As Single
        Price = m_Price
    End Property
    
    ' свойство установки цены
    Public Property Let Price(ByVal a_Price As Single)
        If a_Price <= 800 And a_Price >= 150 Then
            m_Price = a_Price
        End If
    End Property
    

    Обращение к свойству выглядит так:

    clk.Price = 500    ' установить стоимость часов
    MsgBox clk.Price   ' выдать стоимость часов
    

    Не правда ли, похоже на обращение к переменной?

    Последнее, что нужно знать о свойствах. Вы можете создавать свойство, состоящее только из Property Get или Property Let (Property Set). В первом случае свойство будет только для чтения, т. е. неподдающимся для изменения, а во втором случае вы сможете установить свойство, но не прочитать его.

  9. События.
  10. События (Events, ивенты) позволяют строить асинхронные модели, когда объект формирует и выстреливает какое-либо сообщение. Например, вы установили время срабатывания будильника для наших часов. В заданный час будильник сработает, но как объект об этом сообщит? Можно, конечно, завести флажок и взводить его, и при этом по таймеру извне класса постоянно проверять его. Но такой путь нерационален и ведет к лишним затратам. И тут на помощь приходят события.

    Итак, создадим новый ивент в нашем классе Clock:

    ' декларируем публичный ивент
    Public Event Alarm(Melody As String)
    
    ' некая приватная процедура, генерирующая ивент
    Private Sub GenerateAlarm()
        ' некоторые действия
        . . .
    
        ' выстреливаем ивент
        RaiseEvent Alarm("My little star.")
    
    End Sub
    

    Для того, чтобы иметь возможность получать ивенты, внесем некоторые изменения в код создания экземпляра класса. Для этого воспользуемся новым ключевым словом WithEvents:

    Dim WithEvents clk As Clock
    Set clk = New Clock
    

    Если теперь в окне модуля, где создавался инстанс, раскрыть левый комбобокс, то в списке объектов окажется слово clk. Установив его, в списке ивентов правого комбобокса найдем ивент Alarm:

    Private Sub clk_Alarm(Melody As String)
    
    End Sub
    

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

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

  1. Программа будет исполняться на банкоматах фирмы MicroSo, представляющие из себя обычные компьютеры с процессором Celeron, работающие под управлением OS Windows 98.
  2. Все, что не упоминается в требованиях — реализуется хардверно и нас не должно заботить.
  3. После опускания клиентом кредитной карточки и нажатия кнопки банкомат должен осуществить процедуру соединения с банком по телефонной сети. Соединение может не удасться.
  4. Если соединение прошло успешно, вывести клиенту состояние его счета.
  5. Предоставить клиенту возможность изменить счет как в большую, так и в меньшую сторону. После этого провести транзакцию с банком.
  6. В конце работы клиент нажимает кнопку, заканчивается сеанс, происходит рассоединение с банком и клиент получает назад свою карточку.

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

Создадим новый проект и назовем его CreditCard. Добавим к проекту класс CCard. Этот класс будет ведать работой с банком. Управлять классом будет форма, названная winCard. Подобное разделение на независмые блоки полезно тем, что изменяя интерфейс, вы не трогаете класс, ведающий обработкой данных. А в случае изменения способа обмена с банком можно легко заменить класс на другой, не трогая интерфейс, столь полюбившийся клиентам. ;-) Может в крошечных программах это и не так важно, но в больших проектах несоблюдение этого простого правила способно неимоверно усложнить работу программисту. Ради небольших правок он будет вынужден переписывать сотни строк кода. Поэтому лучше с самого начала учиться писать переносимые программы, строить их из независимых друг от друга кубиков. Тогда изъятие или изменение одного кубика не приведет к развалу всего здания проекта.

Для начала разберемся с классом. Он должен содержать метод Connect подключения к серверу банка и метод Disconnect отключения от сервера. Метод Connect должен возвращать True в случае успеха и False, если соединение не прошло. Свойство Account возвращает сумму в банке на счету клиента. Для изменения остатка на счету служит уже другой метод — Change. В случае успеха метод возвращает True, и если транзакция пройти не может (со счета снимают больше, чем он содержит) — False.

К интерфейсу нет особых требований. Он должен обеспечивать подключение и отключение клиента (2 кнопки), предоставлять клиенту информацию о состоянии его счета (поле Label) и позволять клиенту изменить сумму на счету (поле TextBox и кнопка). Вы можете изменить интерфейс как вам заблагорассудится, но это не должно отразиться на работоспособности программы.

А теперь, зная задачу, попробуйте самостоятельно ее выполнить.

Для тех, кто не до конца понял объяснений данной статьи, работающий пример проекта находится в архиве CreditCard. Попробуйте разобрать его, понять, как он работает и устранить те глюки, которые я специально оставил. ;-)

Автор:
Александр Щербаков

Предыдущий раздел Следующий раздел

© 2004. При цитировании, пожалуйста, не забудьте поставить ссылку на оригинальную страницу.