Введение в расширенные типы
 
Автор rdc

Введение


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

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


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

Объект содержит данные, необходимые объекту и методы (подпрограммы и функции), которые действуют на данные. Группировка данных и методов в единое целое, называется инкапсуляцией. Инкапсуляция позволяет создавать модульные блоки, которые могут быть повторно использованы в нескольких программах. Эта идея повторного использования кода была главной мотивацией в создании парадигмы ООП.

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

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

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

Опубликованный контракт


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

Характеристики объекта


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

Предположим, вы хотите создать объект, который рисует прямоугольник на экране. Прямоугольник может иметь несколько свойств, которые будут содержаться в элементах данных объекта. Прямоугольник имеет начальную точку на экране, обычно левый верхний угол, который можно представить элементами X и Y. Прямоугольник имеет ширину и высоту, так что к объекту будет добавлены элементы WIDTH и HEIGHT. Прямоугольник можно сделать полым или заполненным, поэтому добавим элемент данных флаг filled. Конечно, если вы захотите зарисовать его определенным цветом, то объекту будет необходимо иметь элемент данных цвет (fill color) и для гибкости можно добавить еще цвет контура (outline color). Конечно, вам понадобится метод рисования прямоугольника на экране DrawRect.

Так наш объект прямоугольник имеет следующие предварительные свойства и методы:

Свойство: x и y (начальные координаты)
Свойство: width
Свойство: height
Свойство: filled
Свойство: outline color
Свойство: fill color
Метод: DrawRect

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

Определение типа прямоугольник


Следующий фрагмент кода является частичным определением прямоугольника:
Type myRect
  Private:
    X_ As Integer
    Y_ As Integer
    Width_ As Integer
    Height_ As Integer
    Filled_ As Integer
    Otlncolor_ As Integer
    Fillcolor_ As Integer
    Public:
    Declare Sub DrawRect()
End Type

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

Сокрытие информации является способом сохранить целостность объекта. Вы никогда не должны позволять внешнему процессу иметь прямой доступ к элементам данных. Весь доступ к данным должен быть за счет использования соответствующих открытых элементов, так чтобы контролировать то, что посылается вашему объекту. Строгий контроль над данными вашего объекта поможет предотвратить многие ошибки, которые могут возникнуть, когда программист использует ваш объект.
Type myRect
  Private:
    X_ As Integer
    Y_ As Integer
    Width_ As Integer
    Height_ As Integer
    Filled_ As Integer
    Otlncolor_ As Integer
    Fillcolor_ As Integer
    Public:
    Declare Sub DrawRect()
    Declare Property X(ByVal xx_ As Integer)
    Declare Property X() As Integer
    Declare Property Y(ByVal yy_ As Integer)
    Declare Property Y() As Integer
    Declare Property Width(ByVal w_ As Integer)
    Declare Property Width() As Integer
    Declare Property Height(ByVal h_ As Integer)
    Declare Property Height() As Integer
    Declare Property Filled(ByVal f_ As Integer)
    Declare Property Filled() As Integer
    Declare Property Otlncolor(ByVal oc_ As Integer)
    Declare Property Otlncolor() As Integer
    Declare Property FillColor(ByVal fc_ As Integer)
    Declare Property FillColor() As Integer
End Type

Операторы Declare после квалификатора Public включают открытый интерфейс для вашего объекта. Так как переменные типа определяются с ключевым словом Private, то единственный способ для доступа к ним через элементы-свойства (Prtoperty), сохраняя целостность объекта. Поскольку определение кода завязано на свойствах (Property), то это дает возможность полностью контролировать то, что помещается в ваш объект. Распространенным примером этого является поставить код проверки диапазона в вашем свойстве (Property), чтобы объект не принимал недопустимые данные.

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

Правильное создание объектов


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

Конструктор (Constructor) - подпрограмма, которая вызывается при создании объекта с помощью оператора Dim (или New). Конструкторы являются полезными для инициализации объекта значениями по умолчанию, или значениями, которые передаются в конструктор. Обновленное определение теперь выглядит следующим образом:
Type myRect
  Private:
    X_ As Integer
    Y_ As Integer
    Width_ As Integer
    Height_ As Integer
    Filled_ As Integer
    Otlncolor_ As Integer
    Fillcolor_ As Integer
    Public:
    Declare Sub DrawRect()
    Declare Property X(ByVal xx_ As Integer)
    Declare Property X() As Integer
    Declare Property Y(ByVal yy_ As Integer)
    Declare Property Y() As Integer
    Declare Property Width(ByVal w_ As Integer)
    Declare Property Width() As Integer
    Declare Property Height(ByVal h_ As Integer)
    Declare Property Height() As Integer
    Declare Property Filled(ByVal f_ As Integer)
    Declare Property Filled() As Integer
    Declare Property Otlncolor(ByVal oc_ As Integer)
    Declare Property Otlncolor() As Integer
    Declare Property FillColor(ByVal fc_ As Integer)
    Declare Property FillColor() As Integer
    Declare Constructor()
    Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
                        h_ As Integer, f_ As Integer, oc_ As Integer, _
                        fc_ As Integer)
                        
End Type

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

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

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


Заполнение методов объекта


Определение типа представляет собой шаблон для типа объекта и сообщает компилятору, как настроить объект в памяти. Однако для того, чтобы на самом деле использовать объект, вам нужно создать фактические вызовы, как показано в следующем листинге.
Type myRect
  Private:
    X_ As Integer
    Y_ As Integer
    Width_ As Integer
    Height_ As Integer
    Filled_ As Integer
    Otlncolor_ As Integer
    Fillcolor_ As Integer
    Public:
    Declare Sub DrawRect()
    Declare Property X(ByVal xx_ As Integer)
    Declare Property X() As Integer
    Declare Property Y(ByVal yy_ As Integer)
    Declare Property Y() As Integer
    Declare Property Width(ByVal w_ As Integer)
    Declare Property Width() As Integer
    Declare Property Height(ByVal h_ As Integer)
    Declare Property Height() As Integer
    Declare Property Filled(ByVal f_ As Integer)
    Declare Property Filled() As Integer
    Declare Property Otlncolor(ByVal oc_ As Integer)
    Declare Property Otlncolor() As Integer
    Declare Property FillColor(ByVal fc_ As Integer)
    Declare Property FillColor() As Integer
    Declare Constructor()
    Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
                        h_ As Integer, f_ As Integer, oc_ As Integer, _
                        fc_ As Integer)
End Type

Sub myRect.DrawRect()
    Line (this.x_, this.y_)-(this.x_ + Width - 1, this.y_ + this.height_ - 1), this.Otlncolor_, B
    If this.Filled_ <> 0 Then
        Paint (this.x_ + 1, this.y_ + 1), this.Fillcolor_, this.Otlncolor_
    End If   
End Sub

Property myRect.x(ByVal xx_ As Integer)
    this.X_ = xx_
End Property

Property myRect.x() As Integer
    Return this.X_
End Property

Property myRect.y(ByVal yy_ As Integer)
    this.Y_ = yy_
End Property

Property myRect.y() As Integer
     Return this.y_
End Property

Property myRect.Width(ByVal w_ As Integer)
    this.Width_ = w_
End Property

Property myRect.Width() As Integer
    Return this.Width_
End Property

Property myRect.Height(ByVal h_ As Integer)
    this.Height_ = h_
End Property

Property myRect.Height() As Integer
    Return this.Height_
End Property

Property myRect.Filled(ByVal f_ As Integer)
    this.Filled_ = f_
End Property

Property myRect.Filled() As Integer
    Return this.Filled_
End Property

Property myRect.Otlncolor(ByVal oc_ As Integer)
    this.Otlncolor_ = oc_
End Property

Property myRect.Otlncolor() As Integer
    Return this.Otlncolor_
End Property

Property myRect.FillColor(ByVal fc_ As Integer)
    this.Fillcolor_ = fc_
End Property

Property myRect.FillColor() As Integer
    Return this.Fillcolor_
End Property

Constructor myRect
    this.X_ = 0
    this.Y_ = 0
    this.Width_ = 10
    this.Height_ = 10
    this.Filled_ = 0  
    this.Otlncolor_ = 15
    this.Fillcolor_ = 7
End Constructor

Constructor MyRect (xx_ As Integer, yy_ As Integer, w_ As Integer, _
                        h_ As Integer, f_ As Integer, oc_ As Integer, _
                        fc_ As Integer)

    this.X_ = xx_
    this.Y_ = yy_
    this.Width_ = w_
    this.Height_ = h_
    this.Filled_ = f_  
    this.Otlncolor_ = oc_
    this.Fillcolor_ = fc_
End Constructor

Методы и свойства определяются с помощью синтаксиса Sub/Function/Property имя_типа.имя_метода. Это указывает компилятору как сопоставлять методы с определением надлежащего типа. Конструкторы определяются с именем типа по той же причине. Идентификатор this является скрытым параметром, который передается в методы, и который ссылается на определенный тип. Вы использовать идентификатор  this для указания того, что вы хотите получить доступ к конструкции типа.

Использование вашего объекта


Теперь объект укомплектован и может использоваться в программе, которая указана ниже.
Type myRect
  Private:
    X_ As Integer
    Y_ As Integer
    Width_ As Integer
    Height_ As Integer
    Filled_ As Integer
    Otlncolor_ As Integer
    Fillcolor_ As Integer
    Public:
    Declare Sub DrawRect()
    Declare Property X(ByVal xx_ As Integer)
    Declare Property X() As Integer
    Declare Property Y(ByVal yy_ As Integer)
    Declare Property Y() As Integer
    Declare Property Width(ByVal w_ As Integer)
    Declare Property Width() As Integer
    Declare Property Height(ByVal h_ As Integer)
    Declare Property Height() As Integer
    Declare Property Filled(ByVal f_ As Integer)
    Declare Property Filled() As Integer
    Declare Property Otlncolor(ByVal oc_ As Integer)
    Declare Property Otlncolor() As Integer
    Declare Property FillColor(ByVal fc_ As Integer)
    Declare Property FillColor() As Integer
    Declare Constructor()
    Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
                        h_ As Integer, f_ As Integer, oc_ As Integer, _
                        fc_ As Integer)
End Type

Sub myRect.DrawRect()
    Line (this.x_, this.y_)-(this.x_ + this.Width_ - 1, this.y_ + this.height_ - 1), this.Otlncolor_, B
    If this.Filled_ <> 0 Then
        Paint (this.x_ + 1, this.y_ + 1), this.Fillcolor_, this.Otlncolor_
    End If   
End Sub

Property myRect.x(ByVal xx_ As Integer)
    this.X_ = xx_
End Property

Property myRect.x() As Integer
    Return this.X_
End Property

Property myRect.y(ByVal yy_ As Integer)
    this.Y_ = yy_
End Property

Property myRect.y() As Integer
     Return this.y_
End Property

Property myRect.Width(ByVal w_ As Integer)
    this.Width_ = w_
End Property

Property myRect.Width() As Integer
    Return this.Width_
End Property

Property myRect.Height(ByVal h_ As Integer)
    this.Height_ = h_
End Property

Property myRect.Height() As Integer
    Return this.Height_
End Property

Property myRect.Filled(ByVal f_ As Integer)
    this.Filled_ = f_
End Property

Property myRect.Filled() As Integer
    Return this.Filled_
End Property

Property myRect.Otlncolor(ByVal oc_ As Integer)
    this.Otlncolor_ = oc_
End Property

Property myRect.Otlncolor() As Integer
    Return this.Otlncolor_
End Property

Property myRect.FillColor(ByVal fc_ As Integer)
    this.Fillcolor_ = fc_
End Property

Property myRect.FillColor() As Integer
    Return this.Fillcolor_
End Property

Constructor myRect
    this.X_ = 0
    this.Y_ = 0
    this.Width_ = 10
    this.Height_ = 10
    this.Filled_ = 0  
    this.Otlncolor_ = 15
    this.Fillcolor_ = 7
End Constructor

Constructor MyRect (xx_ As Integer, yy_ As Integer, w_ As Integer, _
                        h_ As Integer, f_ As Integer, oc_ As Integer, _
                        fc_ As Integer)

    this.X_ = xx_
    this.Y_ = yy_
    this.Width_ = w_
    this.Height_ = h_
    this.Filled_ = f_  
    this.Otlncolor_ = oc_
    this.Fillcolor_ = fc_
End Constructor

'Создание графического экрана
Screen 18

'Создание объекта с помощью конструктора по умолчанию
Dim aRect As myRect
'Создание объекта, явно устанавливая значения конструктора
Dim bRect As myRect = myRect(200, 200, 200, 100, 1, 15, 9)

'Рисуем прямоугольники на экране
aRect.DrawRect
bRect.DrawRect

'Обновление aRect свойств
aRect.X = 90
aRect.Y = 20
aRect.Filled = 1
aRect.FillColor = 15

'Рисование новой области
aRect.DrawRect
Sleep
End


Для инициализации объекта, используя конструктор по умолчанию, вы просто используете Dim так же, как со стандартным типом. Если конструктор принимает только одно значение, то вы можете использовать синтаксис Dim var as Typename = value. Для инициализации объекта с набором значений, вы можете использовать синтаксис Dim var as Typename = Typename(param1, param2...). Вы можете увидеть, что доступ к элементам объекта такой же, как доступ к элементам стандартного типа.

Спасибо cha0s на FreeBASIC форуме за информацию относительно свойств.