BlitzMax: Объектно-Ориентированное Программирование

Автор: Джон Джудник

Введение

Сначала объектно-ориентированное программирование (ООП) может выглядеть непонятным и ненужным для тех, кто привык к процедурным методам кодинга, но как только вы ознакомитесь с техникой ООП и ее преимуществами, вы никогда не захотите вернуться к процедурному программированию снова. Как видно из названия, ООП, в основном, обеспечивает способ связывать переменные и функции вокруг виртуальных «объектов». К концу этого обучения, у вас должно сложиться хорошее понимание использования и преимуществ ООП — техники.

Типы в BlitzMax, определенные пользователем

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

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

Strict 

Type Car
     Field Image:TImage  
     Field Name:String 
     Field Speed:Float, Rotation:Float 
End Type 

Local a:Int, b:Float, c:Car

Попробуйте запустить пример. Вы удивитесь, что он фактически работает. Да — «Car» теперь настоящий тип переменной! Вы можете передавать его функциям, устанавливать ему значения и тд., точно также как для любой другой переменной. Поля Field добавляют переменные, которые последующие экзепляры типа Car будут содержать внутри. Эти внутренние переменные технически называются «элементами» типа Car. Пользовательский тип в действительности всего лишь удобный способ сгруппировать переменные.

Получить доступ к элементам переменной типа Car очень легко. Просто используйте оператор точки (.) как показано ниже:

Strict 

Type Car
     Field Image:TImage
     Field Name:String
     Field Speed:Float, Rotation:Float
End Type

Local a:Int, b:Float, c:Car

c = New Car

c.Name = "70s Chevy"
Print c.Name

Как видите c.Name обращается к полю Name типа Car переменной «c» . Также как и в примере, Name может быть изменено и прочитано через «c» , как и любая другая переменная.

Вы можете заинтересоваться, что делает запись «c = new Car» . Этот аспект пользовательского типа преимущественно сложно понять в начале. Понимайте его следующим образом: переменная пользовательского типа (в отличие от обычных Float, Int и других переменных) фактически не является переменной — вообразите переменную «c» в этом примере в виде указателя, который вы можете прицепить на любой Car чтобы создать с ним связь. Через этот указатель (переменная «c»), вы можете обращаться к любым данным типа Car (например, к таким c.Name). Между тем, указатель сам по себе является бесполезным, сначала он должен быть присоединен к чему-нибудь. Для того, чтобы присоединить этот указатель (переменную «c») к чем-нибудь, просто назначьте его реальному объекту. А для того, чтобы создать реальный объект, вы используете ключевое слово «new» . Запись «c = New Car» в примере выше создает новый Car и назначает его переменной «c» . Без этой записи, код «c.Name» , расположенный ниже, вызовет ошибку компилятора во время выполнения, поскольку будет пытаться обратиться к несуществующим данным.

Примечание: Когда переменная пользовательского типа ни к чему не прикреплена, ее значение именуется как «Null» . Если вы хотите отсоединить указатель (переменную пользовательского типа) от объекта, просто установите значение переменной на «Null» (например, «c = Null»). Вам следует всегда отсоединять переменные от реальных объектов, в случае, если вам больше не нужно будет к ним обращаться. Реальный объект (созданный при помощи «New») будет всегда оставаться в памяти до тех пор, пока все указатели не будут отсоединены от него. Взгляните на этот пример:

Strict  

Type Car
     Field Image:TImage
     Field Name:String
     Field Speed:Float, Rotation:Float
End Type

Local a:Int, b:Float, c:Car, d:Car

c = New Car

d = c
 
c.Name = "70s Chevy"
Print d.Name

Сначала создается новый Car и назначается переменной «c» . Затем «d» (другой указатель типа Car) назначается переменной «c» . Когда переменная пользовательского типа назначается другой переменной пользовательского типа, как здесь, то «d» прикрепляется к тому же объекту, к которому присоединена переменная «с» . Поэтому теперь обе переменные «c» и «d» будут присоединены к общему объекту типа Car .

Как видите ниже, запись «c.Name» устанавливает для поля Name типа Car значение «70s Chevy». Затем «d.Name» (сейчас это «d», но не «c») отображает на экране надпись «70s Chevy» . Следовательно, и «c» и «d» привязаны к одному и тому же объекту Car, это правильное поведение.

У вас может возникнуть вопрос: какая от этого польза? Настоящее преимущество переменных пользовательского типа — это быть «указателями», а не «реальными объектами», когда вы начинаете представлять фактические объекты реального мира в своей игре. Таким образом, это дает вам полный контроль над созданием/удалением объектов, и вы можете с легкостью передавать существующий объект в функцию. Например:

Strict

Type Car 
     Field Image:TImage
     Field Name:String
     Field Speed:Float, Rotation:Float
End Type 

Local a:Int, b:Float, c:Car
c = New Car 'Создаем новый объект Car и назначаем его переменной c

InitCar(c)

'Пример того, что функция InitCar() фактически изменила объект "c" типа Car
Print c.Speed
Print c.Rotation

c = Null 'Car Больше не нужен

Function InitCar(obj:Car)
     obj.Speed = 0 
     obj.Rotation = 90  
     Return 
End Function

Поскольку переменные типа Car являются всего лишь указателями на реальные данные, функция InitCar() может выполнить операции над существующим объектом. В функции InitCar() , параметр Car (obj:Car) становится прикрепленным к любому объекту типа Car , который был определен. В этом случае, это Car , к которому присоединена переменная «c» . Далее функция изменяет переменные типа Car и возвращает.

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

Основы Объектно-Ориентированного программирования

Для начала взгляните на следующий код, являющийся довольно простым примером традиционной (процедурной) реализации со «счетчиком» (counter) , который просто приращает число, всякий раз, как только функция вызывается:

Strict 

Type TCounter 
     Field Value:Int
End Type 

Function IncrementCounter(counter:TCounter)
     counter.Value :+ 1
End Function

'Создаем счетчик для тестирования
Local test:TCounter = New TCounter

'Отобразить его значение
Print
Print "Значение счетчика равно "+test.Value

'Увеличить значение
IncrementCounter(test)
Print "Значение счетчика было увеличено"

'Теперь отображаем его значение (которое теперь должно быть 
'больше единицы по сравнению с прошлым значением)
Print "Значение счетчика равно " + test.Value

Пример выше создает довольно простой тип (названный TCounter — префикс «T» всего лишь способ обозначения того, что это тип), который содержит поле типа integer (названный Value). Функция названная IncrementCounter() прибавит единицу к переменной Value любого объекта типа TCounter. Остальная часть кода, которая находится ниже, является простым примером взаимодействия функции и типа.

Теперь чтобы преобразовать этот пример в объектно-ориентированную программу нужно сделать несколько небольших изменений. Поскольку ООП позволяет вам вставлять функции вовнутрь описания типа (так как и переменные в него), то функция IncrementCounter может быть помещена в определение типа (однако эта «функция» теперь будет называться «методом»). Например:

Strict 

Type TCounter 
     Field Value:Int

     Method Increment()
          Self.Value :+ 1 
     End Method 
End Type 

Теперь метод Increment() может быть вызван следующим образом:

'Создаем счетчик для тестирования 
Local test:TCounter = New TCounter 
 
'Устанавливаем значение для него
test.Value = 5

'Приращение
test.Increment()

Как видите, метод Increment() получил доступ точно таким же способом к полю Value как и через переменную. Объект (test) продолжается точкой (.) , затем идет имя метода, через которое происходит доступ. В этом случае, значение поля Value объекта test устанавливается на 5 (test.Value = 5). Затем выполняется метод Increment() (test.Increment()).

Вы должно быть заметили, что описание метода Increment() имеет несколько отличий по отношению к функции IncrementCounter() в другом примере. Ключевое слово «Self» используется вместо параметра counter:TCounter. Так как Increment() является элементом типа TCounter , то вы можете использовать «Self» чтобы обращаться к тому объекту, над которым происходит манипулирование. Например:

test.Increment()

При выполнении вызывается метод Increment() :

Method Increment()
     Self.Value :+ 1
End Method

Теперь, после вызова test.Increment() , «Self» фактически ссылается на «test» , поэтому «Self.Value :+ 1» выполняет следующее действие: «test.Value :+ 1».

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

Method Increment()
    Value :+ 1
End Method

В этом случае BlitzMax будет автоматически подразумевать, что вы ссылаетесь на поле Value переменной типа TCounter. Это позволяет программисту более проще сконцентрироваться на перспективе объекта, а не на глобальной перспективе. Например, метод Increment() просто обеспечивает компьютер методом приращения объекта TCounter, и не важно над каким счетчиком происходит манипулирование, и от куда Increment() вызывается — просто пока Increment() делает свою работу как положено, все будет работать безупречно.

Теперь вы понимаете основную концепцию методов, вот более практичный пример, который показывает полезность ООП-техники:

Strict 
 
Type TRobot 
     Field Name:String
     Field x:Float, y:Float
     Field Health:Int

     Method SetName(NewName:String)
          Name = NewName
     End Method

     Method Move(x:Float, y:Float)
          Self.x :+ x
          Self.y :+ y
     End Method

     Method Draw()
          SetColor 255,255,255
          DrawText Name, x - (TextWidth(Name)/2), y - 20

          SetColor 0,0,255
          DrawRect x - 5, y - 5, 10, 10

          SetColor 255,0,0
          DrawRect x - 4, y - 4, 8, 8
     End Method 
End Type

Данный тип TRobot имеет следующие элементы: Name, x, y, Health, SetName() и Move(). Поля (Name,x и y) содержат информацию о robot , в то время как методы (SetName(), Move() и Draw()) обеспечивают действиями, которые могут быть выполнены над любым объектом типа TRobot . Например:

Graphics 640, 480 

Local robot:TRobot = new TRobot

robot.SetName("XB-75b")

While Not KeyHit(KEY_ESCAPE)
    Cls

    robot.Move(1, 1)
    robot.Draw()

    Flip
Wend 

Сначала этот пример создает новый объект типа TRobot (с именем»robot»). Запись robot.SetName(«XB-75b») исполняет метод SetName() типа TRobot (метод SetName() является несколько лишним, поскольку вы могли бы просто написать robot.Name = «XB-75b» , метод SetName() предназначен только для примера).

Основной цикл просто вызывает методы robot.Move(1, 1) и robot.Draw() с каждым кадром. Метод Move() двигает robot на одно значение вперед и на одно значение вниз. Метод Draw() прорисовывает «robot» на экране.

Примечание: Сходство этого подхода с традиционными методами программирования будет примерно таким: MoveRobot(robot, 1, 1) и DrawRobot(robot). Объектно-ориентированный стиль не только более структурирован и интуитивен, но также обеспечивает дополнительными возможностями, такими как наследование и полиморфизм, которые делают программирование взаимодействий сложных объектов довольно легким.

Как видите, использование ООП-техники предполагает модульное проектирование в ваших программах, освобождает ваши мысли от сложной внутренней работы системы и скорее сосредотачивается на более высокоуровневом манипулировании объектами. Когда пишется метод Draw() для объекта, например, то программисту, все что нужно, это сконцентрироваться на одной вещи: проинформировать компьютер, чтобы он нарисовал объект правильно. Как только задача выполнена, вам больше не потребуется снова беспокоится по поводу внутренних операций метода, всякий раз, как только объекту нужна прорисовка, то это также просто как вызвать метод «object.Draw()».

Наследование

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

Если вы делаете игру при помощи BlitzMax, более предпочтительно чтобы вы имели множество различных типов игровых объектов (например, вы можете иметь типы TPlayer, TRobot, TBuilding). Вот короткий пример:

Strict

Type TPlayer 
     Field x:Float, y:Float
     Field Health:Int
End Type

Type TRobot 
     Field x:Float, y:Float
     Field Health:Int
End Type

Type TBuilding 
     Field x:Float, y:Float
     Field Enterable:Int
End Type

Local obj:TPlayer = New TPlayer
obj.x = 1
obj.y = 2

Как видите все три типа имеют много общего, у них у всех есть «x» и «y» переменные, а оба типа TPlayer и TRobot имеют общее поле «Health». Наследование предоставляет способ сделать вид шаблонов Type , на основе которых могут быть построены другие. Например:

Strict

Type TEntity
     Field x:Float, y:Float
End Type

Type TPlayer Extends TEntity
     Field Health:Int
End Type 

Type TRobot Extends TEntity
     Field Health:Int
End Type

Type TBuilding Extends TEntity
     Field Enterable:Int
End Type 

Local obj:TPlayer = New TPlayer
obj.x = 1
obj.y = 2

В этом примере ключевое слово Extends после TPlayer, например, сообщает BlitzMax’у о том, что TPlayer содержит не только «Field Health:Int», но и все остальное , что также содержит TEntity. В итоге любой объект типа TPlayer теперь будет автоматически иметь переменные «x» и «y» (обратите внимание, что запись «obj.x = 1» работает просто замечательно, даже если тип TPlayer специально не содержит «x»).

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

Во-первых, вот такой же пример с методом, добавленным к TEntity:

Strict

Type TEntity
     Field x:Float, y:Float

     Method Draw()
          SetColor 255,255,255
          Plot x, y
     End Method
End Type

Type TPlayer Extends TEntity
     Field Health:Int
End Type

Type TRobot Extends TEntity
     Field Health:Int
End Type

Type TBuilding Extends TEntity
     Field Enterable:Int
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer 
obj.x = 1
obj.y = 2

obj.Draw
Flip

WaitKey

Метод Draw() типа TEntity просто устанавливает точку в место объекта. Теперь, поскольку все TPlayer, TRobot и TBuilding унаследуют свойства типа TEntiy, это означает, что они будут иметь метод Draw() (вы в этом убедитесь, если запустите пример).

Теперь рисование точки должно быть выполнено, если вы хотите отметить местоположение объекта, однако нужно, чтобы все объекты типов TBuilding, TRobot и TPlayer выглядели уникально. Это можно сделать переопределив метод Draw(). Проделать это в действительности также легко, как и добавить метод Draw() типу TBuilding, например, и BlitzMax будет использовать уже его. Например:

Strict

Type TEntity
     Field x:Float, y:Float

     Method Draw()
          SetColor 255, 255, 255
          Plot x, y 
     End Method 
End Type

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method 
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method 
End Type

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawOval x - 5, y - 5, 10, 10
     End Method 
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer 
obj.x = 5
obj.y = 7

obj.Draw
Flip

WaitKey

Когда BlitzMax выполняет запись «obj.Draw», он использует наиболее соответствующий метод. В этом случае, это метод Draw() типа TPlayer (но не метод типа TEntity, потому что он более абстрактен).

Если вы не хотите, чтобы тип TEntity имел метод прорисовки «по умолчанию» (потому что сейчас, он просто рисует точку, если ни один сециальный метод не определен), то вы можете удалить метод Draw() типа TEntity, и запись «obj.Draw» все еще будет работать, потому что TPlayer содержит метод Draw(). Между тем, лучший способ сделать это — добавить запись «Abstract» для метода Draw() типа TEntity. Делая метод «абстрактным» в действительности просто способ указания того, что этот метод пустой и типы уровнем ниже (такие как TBuilding и TPlayer) должны иметь такой же метод (иначе вы получите ошибку компилятора). Например:

Strict

Type TEntity
     Field x:Float, y:Float

     Method Draw() Abstract
End Type 

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method
End Type

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()  
          SetColor 255, 255, 255
          DrawOval x - 5, y - 5, 10, 10
     End Method
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer
obj.x = 5
obj.y = 7

obj.Draw
Flip

WaitKey

Вы, возможно заметили, что запись «Method Draw() Abstract» в типе TEntity не имеет объявления «End Method». Это вызвано тем, что «абстрактный» метод всегда пустой и не содержит данных. Определение «абстрактного» метода в действительности просто указывает, что «все типы, унаследованные от типа TEntity должны иметь метод Draw()». Попробуйте удалить метод Draw() типа TPlayer и запустите программу. Возникнет ошибка компиляции, указывающая вам, что наследники типа TEntity должны иметь метод Draw().

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

Полиморфизм

По правде сказать полиморфизм всего лишь забавное слово для процесса обработки ваших объектов обобщенным способом. Полиморфизм было бы проще понять, если думать о нем следующим образом: в ООП, тип TApple мог бы расширить тип TFruit (поскольку яблоко — это фрукт, разумеется). Теперь вы можете сделать это, например:

Local apple:TApple = New TApple
Local orange:TOrange = New TOrange

Local fruit:TFruit

fruit = apple
fruit = orange

Переменная fruit:TFruit, имеет возможность содержать яблоки, апельсины или любой другой тип, который расширяет тип TFruit. Другими словами, при помощи обобщенной переменной (переменная типа TFruit или переменная типа TEntity, например) вы можете сохранять любые объекты расширенных типов. Это означает, что объект типа TPlayer, TRobot или TBuilding может быть сохранен в переменной типа TEntity. Это называется полиморфизмом. Например, вы можете сделать следующим образом:

Graphics 640, 480

Local player:TPlayer = New TPlayer 
player.x = 5
player.y = 7

Local entity:TEntity
entity = player
entity.Draw()

Как видите, переменная типа TEntity используется, чтобы сослаться на тип TPlayer. Запись «entity.Draw()» сообщает компьютеру, чтобы он нарисовал объект типа TEntity. В этом случае, TEntity в действительности является типом TPlayer , поэтому применяется метод Draw() типа TPlayer (но не TEntity). Использовался ли TPlayer, TRobot или TBuilding, не имеет значения, соответствующий метод Draw() будет автоматически выполнен.

Между тем, при обращении к TPlayer, TRobot через переменную TEntity как здесь, вы получите доступ только к полям и методам, включенным в описание TEntity, единственные элементы, можете быть уверены, которые объект будет содержать — это элементы типа TEntity — или что-нибудь еще (в зависимости от того, используется ли TPlayer, TRobot или TBuilding).

Вы могли задаться вопросом чем это полезно. Главное преимущество полиморфизма становится очевидным в случае, если вы хотите сделать что-нибудь со всеми объектами главного типа. Например, что, если вы захотели выполнить метод Draw() для каждого типа TPlayer, TRobot и TBuilding? Обычно, вам нужно было бы иметь список всех игроков, всех домов и всех «robot» по отдельности (что привело бы к неразберихе, особенно если бы вы позже решили бы добавить наследованные типы TEntity). Решение — использовать список для них с типом TEntity, поскольку переменная TEntity имеет полиморфную способность сохранять в себе типы TPlayer, TRobot и TBuilding. Затем вы можете вызвать метод Draw() для них когда вам необходимо, поскольку он является общим для всех объектов типа TEntity. Например:

Graphics 640, 480

Local EntityList:TList = New TList

Local obj:TPlayer = New TPlayer 
obj.x = 5
obj.y = 7
EntityList.AddLast(obj)

Local obj2:TBuilding = New TBuilding
obj2.x = 15
obj2.y = 3
EntityList.AddLast(obj2)

'Отобразить все объекты
Local ent:TEntity
For ent = EachIn EntityList
     ent.Draw()
Next

Flip
WaitKey

Примечание: Если вы не знакомы с модулем TList BlitzMax, он просто обеспечивают легкий способ управления списком объектов, так же как содержание их в массиве.

Полиморфизм полезен везде, где было бы преимущество от обобщения. Например, если у вас есть универсальная функция, которая принимает TEntity в качестве параметра, означает то, что ваши игроки, дома и «robots» будут работать безупречно при помощи нее! Например:

Graphics 640, 480

Local player:TPlayer = New TPlayer 
player.x = 5
player.y = 7

Local house:TBuilding = New TBuilding
house.x = 15
house.y = 3

While Not KeyHit(KEY_ESCAPE)
     Cls

     DrawAndMove(player)
     DrawAndMove(house)

     Flip
Wend

WaitKey

Function DrawAndMove(entity:TEntity)
     entity.Draw
     entity.x :+ 1
     entity.y :+ 1
End Function

Конструкторы и деструкторы

Сначала взгляните на следующий пример (в основном на раздел, где переменные obj и obj2 добавляются в список EntityList):

Strict

Type TEntity
     Field x:Float, y:Float

     Method Draw() Abstract
End Type 

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method
End Type 

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method 
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawOval x - 5, y - 5, 10, 10
     End Method 
End Type 

Graphics 640, 480

Local EntityList: TList = New TList

Local obj:TPlayer = New TPlayer
obj.x = 5; obj.y = 7
EntityList.AddLast(obj)

Local obj2:TPlayer = New TPlayer
obj2.x = 15; obj2.y = 3
EntityList.AddLast(obj2)

'рисуем все объекты
Local ent:TEntity

For ent = EachIn EntityList
          ent.Draw()
Next

Flip
WaitKey

Имеется одна вещь, которую можно было бы рассматривать запутанной или утомительной в верхнем примере: потребность вызывать «EntityAddLast()» для каждого нового объекта-потомка TEntity (если бы он не был добавлен в список, то после он не был бы отображен). А не было ли бы намного проще, если бы это происходило автоматически? Например:

Local obj:TPlayer = New TPlayer 
obj.x = 5; obj.y = 7

Local obj2: TBuilding = New TBuilding
obj2.x = 15; obj2.y = 3

Было бы намного проще, если бы эти объекты могли автоматически где-нибудь добавлять сами себя в список. К счастью это может быть выполнено в ООП! Любой тип можете иметь специальный «метод-конструктор», который вызывается автоматически, как только создается объект. Конструктор может делать все, что вы захотите, также как делает это любой другой метод (в этом случае, он будет добавлять TEntity в список). Чтобы добавить конструктор типу просто добавьте метод с названием «New». Этот метод будет запускаться всякий раз, как только объект этого типа будет создан.

В примере выше лучшее место для установки конструктора — это описание типа TEntity, таким образом, TPlayer, TBuilding и все другие типы-потомки будут также наследовать конструктор. И если необходимо, некоторые из типов могут перезаписать «метод-конструктор». Между тем, переопределение конструктора не тоже самое, что переопределение обычного метода (фактически, он не перезаписывается вообще), скорее всего вместо выполнения нового метода, который перезаписал оригинальный метод, новый «метод-конструктор» будет выполнен дополнительно с оригинальным. Причина этого должна будет проясниться в этом примере:

Strict 

Global EntityList:TList = New TList 

Type TEntity 
     Field x:Float, y:Float

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method 
End Type 

Type TPlayer Extends TEntity 
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method

     Method New()
          Health = 100
     End Method  
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method

     Method New()
          Health = 100
     End Method 
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method 
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer 
obj.x = 5; obj.y = 7

Local obj2:TBuilding = new TBuilding
obj2.x = 15; obj2.y = 3

'Рисуем все объекты
Local ent:TEntity

For ent = EachIn EntityList
          ent.Draw()
Next

Flip
WaitKey

При создании объекта типа TPlayer сначала вызывается конструктор унаследованного типа TEntity, которой, как положено, добавляет объект типа TPlayer в список EntityList. После чего конструктор типа TPlayer вызывается автоматически (запомните, что конструкторы не могут быть переопределены как обычные методы), который устанавливает значение health переменной player на 100 процентов. Затем, создается TBuilding. Вместе с TBuilding вызывается только унаследованный конструктор типа TEntity, поскольку ни одного конкретного кода для конструктора не было определено.

После того, как TBuilding был добавлен в список EntityList через конструктор типа TEntity, все элементы в списке прорисовываются и показывают, что оба экземпляра типа TEntity были добавлены правильно.

В дополнение к конструкторам существуют также деструкторы, которые выполняются точно также за исключением используемого слова «Delete» вместо «New» в качестве имени метода. Деструкторы в BlitzMax могут быть использованы, чтобы выполнить некоторые финальные шаги перед удалением объекта. Примечание: Поскольку в BlitzMax используется система сбора мусора для удаления объектов, не полагайтесь на деструктор, вызываемый в определенное время, метод Delete() (деструктор) будет вызываться всякий раз, как только «сборщик мусора» доходит до него.

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

Статичные методы и поля

Как вы знаете, тип является группой из методов и полей. Значения полей и операции над методами полностью зависят с каким объектом они работают. Например, «car1.x» может быть отличен от «car2.x». В ООП вы также можете включить то, что называется статичными методами и переменными. Статичная переменная доступна всем объектам одного типа. Статичные переменные в типах, в действительности, не отличаются от стандартных глобальных переменных, но, вместе с тем, имеют преимущество сохранения кода в более объектно-ориентированный. Например, взгляните на пример из последнего раздела:

Strict

Global EntityList:TList = New TList

Type TEntity
     Field x:Float, y:Float

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method 
End Type 

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255  
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 
End Type 

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100 
     End Method 
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer 
obj.x = 5; obj.y = 7

Local obj2:TBuilding = New TBuilding
obj2.x = 15; obj2.y = 3

'Рисуем все объекты 
Local ent:TEntity

For ent = EachIn EntityList
          ent.Draw()
Next

Flip
WaitKey

Теперь запись «Global EntityList:TList = New TList» может быть перемещена вовнутрь типа TEntity:

Strict

Type TEntity
     Field x:Float, y:Float
     Global EntityList:TList = New TList

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method 
End Type 

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 
End Type 

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method

     Method New()
          Health = 100
     End Method
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method 
End Type

Graphics 640, 480

Local obj:TPlayer = New TPlayer
obj.x = 5; obj.y = 7

Local obj2:TBuilding = New TBuilding
obj2.x = 15; obj2.y = 3

'Рисуем все объекты
Local ent:TEntity

For ent = EachIn TEntity.EntityList
          ent.Draw()
Next

Flip
WaitKey

Таким образом работают статичные поля в BlitzMax, вместо использования ключевого слова «Field» для определения переменной, используется слово «Global». Ниже вы можете обратить внимание на использование записи «TEntity.EntityList». Поскольку статичное поле EntityList разделяется между всеми объектами типа TEntity, вы также можете использовать «TEntity.» чтобы обратиться к нему. Это становится полезным в основном когда вы не знаете существуют ли какие-либо объекты типа TEntity (да, к статичным полям можно обращаться, даже в случае, если не существует ни одного объекта такого типа).

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

BlitzMax также поддерживает статичные методы. Также как статичные поля, которые являются глобальными переменными, ассоциированными с типом, статичные методы также являются простыми функциями ассоциированными с типом. Статичный метод может быть использован для различных целей, хотя более общее его предназначение — это форма инициализации. Например, вы могли бы создать функции CreatePlayer(), CreateRobot() и CreateBuilding() чтобы сделать более легкую инициализацию определенных свойств объектов при их создании (поскольку функции могли бы позволить вам включить параметры, такие как x, y):

Strict

Type TEntity
     Field x:Float, y:Float
     Global EntityList:TList = New TList

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method
End Type 

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method

     Method New()
          Health = 100
     End Method
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method 
End Type 

Function CreatePlayer:TPlayer(x:Float, y:Float, Health:Int)
     Local ent:TPlayer = New TPlayer 
     ent.Health = Health
     ent.x = x; ent.y = y
     Return ent
End Function

Function CreateRobot:TRobot(x:Float, y:Float, Health:Int)
     Local ent:TRobot = New TRobot 
     ent.Health = Health
     ent.x = x; ent.y = y
     Return ent
End Function

Function CreateBuilding:TBuilding(x:Float, y:Float, Enterable:Int)
     Local ent:TBuilding = New TBuilding 
     ent.Enterable = Enterable
     ent.x = x; ent.y = y
     Return ent
End Function

Graphics 640, 480

Local obj:TPlayer = CreatePlayer(5, 7, 100)
Local obj2:TBuilding = CreateBuilding(15, 3, False)

'Отрисовываем все объекты
Local ent:TEntity

For ent = EachIn TEntity.EntityList
          ent.Draw()
Next

Flip
WaitKey

Как видите использование функций для инициализации объекта имеет преимущества, однако использование их подобным способом является не совсем объктно-ориентированым. Также как глобальная переменная EntityList была помещена в описание типа, функции также могут быть помещены туда. Таким образом, весь код, связанный с игроком теперь ассоциируется с объектом типа TPlayer, весь код связанный со зданиями теперь ассоциируется с объектом типа TBuilding:

Strict 

Type TEntity 
     Field x:Float, y:Float
     Global EntityList:TList = New TList

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method
End Type 

Type TPlayer Extends TEntity
     Field Health:Int 

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 

     Function Create:TPlayer(x:Float, y:Float, Health:Int)
          Local ent:TPlayer = New TPlayer
          ent.Health = Health
          ent.x = x; ent.y = y
          Return ent 
     End Function
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 

     Function Create:TRobot(x:Float, y:Float, Health:Int)
          Local ent: TRobot = New TRobot
          ent.Health = Health
          ent.x = x; ent.y = y
          Return ent
     End Function
End Type

Type TBuilding Extends TEntity
     Field Enterable:Int

     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method 

     Function Create:TBuilding(x:Float, y:Float, Enterable:Int)
          Local ent:TBuilding = New TBuilding
          ent.Enterable = Enterable
          ent.x = x; ent.y = y
          Return ent
     End Function
End Type 

Graphics 640, 480

Local obj:TPlayer = TPlayer.Create(5, 7, 100)
Local obj2:TBuilding = TBuilding.Create(15, 3, False)

'Рисуем все объекты
Local ent:TEntity

For ent = EachIn TEntity.EntityList
          ent.Draw()
Next

Flip
WaitKey

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

Strict

Type TEntity
     Field x:Float, y:Float
     Global EntityList:TList = New TList

     Method Draw() Abstract

     Method New()
          EntityList.AddLast(Self)
     End Method   

     Function DrawAll()
          Local ent:TEntity
          For ent = EachIn EntityList
               ent.Draw()
          Next
     End Function
End Type

Type TPlayer Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 0, 0, 255
          DrawOval x, y, 5, 5
     End Method 

     Method New()
          Health = 100
     End Method 

     Function Create:TPlayer(x:Float, y:Float, Health:Int)
          Local ent:TPlayer = New TPlayer
          ent.Health = Health
          ent.x = x; ent.y = y
          Return ent 
     End Function 
End Type

Type TRobot Extends TEntity
     Field Health:Int

     Method Draw()
          SetColor 255, 0, 0
          DrawOval x, y, 5, 5
     End Method

     Method New()
          Health = 100
     End Method 

     Function Create:TRobot(x:Float, y:Float, Health:Int)
          Local ent: TRobot  = New TRobot 
          ent.Health = Health
          ent.x = x; ent.y = y
          Return ent
     End Function
End Type 

Type TBuilding Extends TEntity
     Field Enterable:Int
   
     Method Draw()
          SetColor 255, 255, 255
          DrawRect x - 5, y - 5, 10, 10
     End Method 

     Function Create:TBuilding(x:Float, y:Float, Enterable:Int)
          Local ent:TBuilding = New TBuilding
          ent.Enterable = Enterable
          ent.x = x; ent.y = y
          Return ent
     End Function
End Type

Graphics 640, 480

Local obj:TPlayer = TPlayer.Create(5, 7, 100)
Local obj2:TBuilding = TBuilding.Create(15, 3, False)

'Рисуем все объекты
TEntity.DrawAll()

Flip
WaitKey

Подведение итогов

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

Комментировать

Почта не публикуется.Обязательные поля отмечены *