Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Тема-05-03испр.docx
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
376.1 Кб
Скачать

5.3.4. Создание объектной модели и приложений с использованием классов

Итак, напомним, что такое объект.

Объект - это синтез данных (в частности, переменных и констант) и действий (процедур), которые эти данные обрабатывают.

Объекты в программировании напоминают объекты реального мира, которые в некотором смысле представляют синтез данных и действий.

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

Пример 5.3-7. Определение объектной модели игрушечного радиоуправляемого автомобиля.

Определим, где у игрушечного радиоуправляемого автомобиля данные и где действия.

Данных у игрушечного автомобиля множество. Например, следующие:

  • цвет кузова;

  • номер автомобиля;

  • скорость движения в данный момент;

  • громкость звукового сигнала;

  • высота кресел;

  • величина электрического тока в двигателе в данный момент;

  • толщина гайки в таком-то месте внутри корпуса и т. д.

Действий тоже достаточно. Например:

  • поворот по команде с пульта управления;

  • торможение по команде с пульта управления;

  • подпрыгивание автомобиля на маленьком камушке;

  • изменение скорости вращения электродвигателя при изменении в нем тока;

  • возникновение жужжания двигателя при трении шестеренок друг о друга и т. д.

Определим "внешние" и "внутренние" данные и действия. Они бывают крупные и мелкие, важные и менее важные. Нас будут интересовать:

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

  • данные и действия, что не видны или недоступны (т.е. близки к понятию модульных).

Проведем дальнейшую детализацию и определим, какие данные являются свойствами. Таким образом, данные будем делить на те, что видны снаружи (это первые 5 свойств из списка), и те, что не видны (последние два). Данные, видимые снаружи, назовем свой­ствами объекта

Итак, свойства - это данные, видимые снаружи. Теперь будем подразделять свойства по доступности к изменению. Мы их разделим на две категории:

  • те, что можно произвольно менять снаружи (назовем их свойствами для чтения- записи);

  • те, что снаружи менять нельзя - последние 4 данных из списка (назовем их свойствами только для чтения).

Очевидно, что данные, которые не видны снаружи, и менять снаружи тоже нельзя. В программировании это локальные переменные или модульные переменные, объявлен­ные модификаторами Dim или Private.

Действия разделим на те, которые можно вызывать снаружи (первые два действия из списка), и на те, что вызываются внутренним строением автомобиля (ос­тальные). Действия, вызываемые снаружи, назовем методами объекта. В программиро­вании это процедуры, объявленные, например, словом Public. Если процедуры объявлены словом Private, то снаружи они не видны и методами не явля­ются.

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

  • количество методов должно быть минимально необходимым (например, разгон, торможение, поворот налево и направо и этого достаточно);

  • количество свойств для чтения-записи должно быть минимальным (если все данные сделать свойствами, да еще и для чтения-записи, тогда любой, кому не лень, сможет в любой момент менять все свойства).

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

Данные и действия объекта представляют собой единое целое, образующее всю ме­ханику объекта, и хранятся они в одной "упаковке". Упаковкой этой является класс. Они должны быть как можно меньше видимы снаружи. Хорошо ин­капсулированный объект представляет собой некий "черный ящик". Вся работа идет внутри. Внутренние данные меняются при помощи внутрен­них действий. Никто снаружи не может вмешаться в эту внутреннюю работу. Наружу показывается лишь тот минимум, который необходим для связи с окру­жающим миром.

Влиять снаружи на работу объекта можно только тремя способами:

  • методами;

  • изменяя значения свойств для чтения-записи;

  • изменяя свойства окружающей среды.

Таким образом, инкапсуляция – то, что объединяет объекты в программировании с объектами реального мира.

Теперь можно уточнить терминологию.

Данные, видимые снаружи, делятся на свойства и на поля.

Полем (Field) класса будем называть переменную, объявленную в этом классе при помощи Public или Friend:

Public С As Integer = 10

Свойства (Properties) определяются при помощи ключевого слова Property и описаны в примере 5.3-4. Пока же будем иметь дело только с полями.

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

Пример 5.3-8. Создание и использование двух объектов одного класса.

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

Надо создать класс Участок, в котором собрать все данные, характеризующие участок (например, размеры), и действия для вычисления нужных ве­личин, касающихся участка (например, его площади).

Полный программный код приведен на рис. 5.3-9.

Предполагается, что на форме имеется 6 текстовых по­лей (TextBox) и 3 кнопки (Button), а проект должен действовать следующим образом.

В 5 текстовых полей вводится следующая информация, касающаяся первого участка:

TextBox1 Владелец участка;

TextBox2 Длина участка;

TextBox3 Ширина участка;

TextBox4 Высота забора на участке;

TextBox5 Расход краски на 1 кв. м забора.

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

Затем пользователь вводит в текстовые поля информацию, касающуюся второго участка и нажатием на кнопку Button2 создает из класса Участок объект Участок2, который также принимает в себя все данные из текстовых полей в качестве своих полей.

С этого момента в памяти находятся два объекта-экземпляра класса Участок. Програм­мист теперь как угодно может использовать их в своих целях. В качестве примера такого использования он может нажать на кнопку Button3 для решения следующей задачи: "Правда ли, что на покраску забора первого участка уйдет больше краски, чем для второго?"

Public Class Участок

Public Владелец As String

Public Длина, Ширина, Высота_забора As Integer

Public Расход_краски_на_кв_м As Integer

Private Периметр As Integer

Private Sub Вычисляем_периметр( )

Периметр = 2 * (Длина + Ширина)

End Sub

Private Function Площадь_забора( ) As Integer

Вычисляем_периметр( )

Return Периметр * Высота_забора

End Function

Public Function Расход_краски_на_забор( ) As Integer

Return Расход_краски_на_кв_м * Площадь_забора()

End Function

End Class

Public Class Form1

Dim Участок1, Участок2 As Участок

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs)_

Handles Button1.Click

Участок1 = New Участок 'Из класса рождается объект.

Участок1.Владелец = TextBox1.Text Участок1.Длина = CInt(TextBox2.Text)

Участок1.Ширина = CInt(TextBox3.Text)

Участок1.Высота_забора = CInt(TextBox4.Text)

Участок1.Расход_краски_на_кв_м = CInt(TextBox5.Text)

End Sub

Private Sub Button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs)_

Handles Button2.Click

Участок2 = New Участок 'Из класса рождается объект

Участок2.Владелец = TextBox1 Text

Участок2.Длина = TextBox2.Text

Участок2.Ширина = TextBox3.Text

Участок2.Высота_забора = TextBox4.Text

Участок2.Расход_краски_на_кв_м = TextBox5.Text

End Sub

Private Sub Button3_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs)_

Handles Button3.Click

If Участок1.Расход_краски_на_забор > Участок2.Расход_краски_на_забор Then

TextBox6.Text="Пepвомy участку нужно больше краски, чем второму"

End If

End Sub

End Class

Рис. 5.3-9

В классе объявлено 5 переменных с ключевым словом Public. Следовательно, они видны снаружи объекта. Значит, это поля объекта. К тому же Public без употребления других запретительных слов допускает свободное изменение значений поля снаружи объекта. Следовательно, это поля для чтения-записи.

Внутренняя механика объекта определяется его процедурами и функциями.

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

Процедура Вычисляем_периметр( ) вычисляет периметр. Сделали ее Private, так как пока никому снаружи она не нужна, всех интересует только расход краски. Если в будущем кому-то захочется вычислять периметр участка, поменяем Private на Public. По той же причине объявлена Private и переменная Периметр, которая ввиду этого полем не является и снаружи не видна. Класс Участок использует ее только как промежуточ­ный результат вычислений.

Функция Площадь_забора( ) возвращает площадь забора. Мы сделали ее Private из аналогичных соображений.

Функция Расход_краски__на_забор( ) возвращает результат, который нужен снаружи (в нашем конкретном случае - процедуре Button3_Click( ), принадлежащей форме), поэтому сделали ее Public. Следовательно, эта функция является методом.

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

Пример 5.3-9. Создание и использование массива объектов одного класса.

Предположим, что садовое товарищество растет, и в нем уже несколько десятков участков. Надо менять проект. Теперь у нас на форме будет только две кнопки. Рождение каждого нового объекта будет происходить при нажатии одной и той же Button1. Когда все объ­екты будут рождены, их можно как угодно использовать в своих целях. В качест­ве примера конкретной реализации, рассмотрим ситуацию, когда от нажатия на Button2 распечатываются имена вла­дельцев тех участков, на заборы которых ушло больше 200 кг краски (рис. 5.3-10).

Public Class Участок

Public Номер_участка As Integer

Public Владелец As String

Public Длина, Ширина As Integer

Public Высота_забора As Integer

Public Расход_краски_на_кв_м As Integer

Private Периметр As Integer

Private Sub Вычисляем_периметр()

Периметр = 2 * (Длина + Ширина)

End Sub

Private Function Площадь_забора() As Integer

Вычисляем_периметр()

Return Периметр * Высота_забора

End Function

Public Function Расход_краски() As Integer

Return Расход_краски_на_кв_м * Площадь_забора()

End Function

End Class

Public Class Form1

Dim Участки(100) As Участок

Dim k As Integer = 1

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs)_

Handles Button1.Click

Участки(k) = New Участок

Участки(k).Номер_участка = k

Участки(k). Владелец = TextBox1.Text

Участки(k).Длина = CInt(TextBox2.Text)

Участки(k).Ширина = CInt(TextBox3.Text)

Участки(k).Высота_забора = CInt(TextBox4.Text)

Участки(k).Расход_краски_на_кв_м = CInt(TextBox5.Text)

k = k + 1

End Sub

Private Sub Button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs)_

Handles Button2.Click

Dim i As Integer

For i = 1 To k -1 'Всего участков k-1

If Участки(i).Расход_краски > 200 Then

TextBox6.Text = Учacтки(i).Владелец

End If

Next

End Sub

End Class

Рис. 5.3-10

Здесь вместо двух участков (Участок1 и Участок2) был объявлен массив из 101 участка.

От нажатия к нажатию Button1 значение переменной к увеличивается на единицу оператором k = k + 1.

Поэтому оператор

Участки(k) = New Участок

рождает новый объект - экземпляр класса Участок - и заодно присваивает его очередному элементу массива Участки( ).

В классе Участок прибавилось новое поле - Номер_участка. В принципе снаружи оно никому не нужно, так как если мы обращаемся, скажем, к элементу массива Участки(8), то тем самым мы уже знаем, что это участок № 8.

Пример 5.3-10. Использование функции вместо поля.

Вернемся к предыдущему примеру и предположим, что кому-то сна­ружи класса понадобился периметр участка, а он его не видит, так как он объявлен Private.

Сделаем его полем: Public Периметр As Integer. Теперь он виден всем. И это хорошо. Плохо то, что теперь все смогут менять наш периметр, например, оператором Участки(k).Периметр = 25. Тогда вернем, все как было: Private Периметр As Integer.

Но теперь недоволен заказчик: ему нужно знать периметр. Что же делать? Нужно устроить так, чтобы периметр был все-таки виден и в то же время защищен от пося­гательств. Читать - читайте, а менять извне его значение нельзя!

Для этого создадим функцию Периметр_участка( ):

Public Function Периметр_участка( ) As Integer

Return Периметр

End Function

Теперь любой желающий извне может узнать значение периметра.

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

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

Public Function Периметр_участка() As Integer

If Периметр > 10000 Then

MsgBox("Периметр участка засекречен")

Return 0

Else

Return Периметр + 1

End If

End Function

Идея «функция вместо поля» в VB оформлена синтаксически в виде следующей заготовки:

Public Property Периметр_участка( ) As Integer

Get

End Get

Set(ByVal Value As Integer)

End Set

End Property

Слово Property означает свойство. Периметр участка перестал быть просто переменной, перестал быть полем, перестал быть функцией, он стал свойством.

Слово Get означает «Получи значение свойства», а слово Set означает «Уста­нови значение свойству».

Эта конструкция состоит из двух частей. Внутри части Get - End Get пишется код, показывающий значение свойства наблюдателю из­вне. Внутри части Set - End Set пишется код, позволяющий наблюдателю извне менять значение свойства. Об этой части поговорим чуть позже, а поскольку никто не собирается давать воз­можность изменения значений свойства извне, просто пока сотрем эту часть, а добавим в объявление свойства слово ReadOnly (только для чтения):

Public ReadOnly Property Периметр_участка( ) As Integer

Get

Return Периметр

End Get

End Property

Поставленная задача решена.

Теперь займемся полями нашего класса, например, полем Длина. Хорошо бы при присвоении этому полю значения из текстового поля осуществлялся некоторый контроль значений. Например, мы хотим запретить участки длиннее 500 м. Однако поле Длина имеет тип Integer и поэтому допускает очень большие числа. Можно написать ограничение в коде формы, но это будет нарушением прин­ципа инкапсуляции. Общепринятое средство - создать свойство.

Превратим переменную Длина из поля в модульную (локальную) переменную, защитив ее тем самым от воздействия извне:

Private Длина As Integer

а затем создадим свойство Длина_участка:

Public Property Длина_участка() As Integer

Set (ByVal Value As Integer)

If Value < 500 Then

Длина = Value

Else

MsgBox("Cлишком длинный участок")

Длина = 0

End If

End Set

Get

Return Длина

End Get

End Property

Таким образом, предоставлена возможность каждому извне узнавать без ограничений значение свойства, поэтому часть Get - End Get заполняется стандартно.

Поговорим о части Set - End Set. Внутренним хранителем значения длины в нашем классе является модульная переменная Длина. Свойство Длина_участка придумано только для того, чтобы показывать вовне значение этой переменной и по возможности безопасным и контролируемым образом разрешать извне это значение менять. Часть Set - End Set поэтому имеет своей конечной целью присвоение переменной Длина нужного значения.

Если часть Get - End Get - это функция, то часть Set - End Set - это процедура с параметром Value. Каждый раз, когда кто-то пытается извне присвоить свойству Длина_участка какое-нибудь значение, это значение приходит в объект-участок в качестве значения параметра Value свойства Длина_участка. Остается только написать в части Set - End Set единственный оператор Длина = Value.

И если бы он остался единственным, то в этом случае мы получили бы свойство для чтения-записи безо всяких ограничений. Но ведь по­ле Длина обеспечивало, то же самое. Однако мы помнили о нашем запрете на длинные участки и поэтому часть Set - End Set сделали длиннее. Теперь каждый желающий из текстового поля задать длину участка, равную 500 и выше, столкнется с сообщением «Слишком длинный участок» и вынужден будет вводить данные вновь.

Обратите внимание, что классу совершенно все равно, что физически является передатчиком значения извне внутрь свойства. Это может быть текстовое поле, как в нашем случае, или InputBox( ), или файл. В любом случае значение приходит в объект в качестве значения параметра Value и анализируется согласно коду, написанному в теле свойства программистом.

Итак, обычная последовательность создания свойств:

  • организуется модульная переменная Private – хранительница значения свойства;

  • организуется свойство Property для связи этой переменной с внешним миром.

Если же эта переменная вам внутри класса не нужна, то вы можете обойтись и без нее и вычислять значение свойства прямо в теле свойства в части Get - End Get.

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

Для этого превращаем поле Владелец в модульную переменную:

Private Владелец As String

Тогда свойство Владелец_участка будет следующим:

Public WriteOnly Property Владелец_участка( ) As String

Set (ByVal Value As String)

Владелец = Value

End Set

End Property

Теперь рассмотрим конструкторы (специальные процедуры с именем New). Обратите внимание, что эти процедуры могут выполняться автоматически при создании объекта.

Для чего же нужен New? Для того же, для чего, и процедура Form1_Load( ) (ведь форма загружалась при отсутствии этой процедуры), а именно для того, чтобы произвести некоторые действия, которые программист считает нужным произвести в момент создания объекта из класса. Обычно это присвоение начальных значений переменным, открытие нужных файлов и т. п.

Пример 5.3-11. Использование конструктора для присваивания значения.

Рассмотрим использование конструктора для присвоения в каждом объекте нужного значения полю Номер_участка. Ранее оно присваивалось в модуле формы, что противоречит принципу инкапсуляции.

Уберем из кода формы строку,

Участки(k).Номер_участка = k

а вместо этого дополним наш класс:

Public Номер_участка As Integer

Private Shared Число_созданных_объектов As Integer = 0

Public Sub New()

Число_созданных_объектов = Число_созданных_объектов + 1

Номер_участка = Число_созданных_объектов

End Sub

При создании очередного участка увеличивается на единицу статическая переменная класса Число_созданных_объектов. Поэтому к моменту создания очередного участка эта переменная автоматически приобретает нужное значение, которое оператором:

Номер_участка = Число_созданных_объектов

придается полю Номер_участка.

Конструктор, как и всякая процедура, может иметь параметры. Обычно параметры используются для задания начальных значений переменным, полям и свойствам класса. Зададим через параметры имя владельца, длину, ширину участка и высоту забора.

В учебных целях сделаем переменную Ширина модульной перемен­ой.

Public Class Участок

Public Владелец As String

Private Длина As Integer

Public Property Длина_участка() As Integer

Get

Return Длина

End Get

Set (ByVal Value As Integer)

If Value < 500 Then

Длина = Value

Else

MsgBox("Cлишком длинный участок") : Длина = 0

End If

End Set

End Property

Private Ширина As Integer

Public Высота_забора As Integer

Public Shared Расход_краски_на_кв_м As Integer

Private Периметр As Integer

Public Номер_участка As Integer

Private Shared Число_созданных_объектов As Integer = 0

Public Sub New (ByVal Влад As String, ByVal Дл As Integer, _

ByVal Шир As Integer, ByVal Выс As Integer)

Владелец = Влад

Длина_участка = Дл

Ширина = Шир

Высота_забора = Выc

Число_созданных_объектов = Число_созданных_объектов +1

Номер_участка = Число_созданных_объектов

End Sub

Private Sub Вычисляем_периметр()

Периметр = 2 * (Длина + Ширина)

End Sub

Private Function Площадь_забора() As Integer

Вычисляем_периметр()

Return Периметр * Высота_забора

End Function

Public Function Расход_краски() As Integer

Return Расход_краски_на_кв_м * Площадь_забора()

End Function

End Class

Для проверки работы конструктора необходимо нажать на кнопку:

Dim Участки(100) As Участок

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles Button1.Click

Dim SS As String = ""

Участок.Расход_краски_на_кв_м = 3

Участки(1) = New Участок("Иванов", 5,4,2)

SS=SS & (Учacтки(1).Pacxoд_кpacки)

Участки(2) = New Участок("Петров", 800,5,3)

SS = SS & Учacток(2).Pacxoд_кpacки)

TextBox1.Text=SS

End Sub

Как видите, при помощи параметров конструктора мы можем задать значения поля Владелец и Высота_забора свойству Длина_участка, а также добраться извне до модульной переменной Ширина, невидимой снаружи.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]