Introduction to the Extended Type
 
Written by rdc

介绍


FreeBASIC正在推行面向对象编程。虽然类尚未添加到语言中,但是Type定义已经被扩展为包括一些面向对象的构造,作为完全类支持的第一步。本文介绍了面向对象设计的一些概念,并介绍了一些扩展类型的构造。

面向对象编程


面向对象编程(通常缩写为OOP)是一种使程序员能够构建称为对象的代码单元的方法。一个对象是一件事情它是代表需要在程序中操纵的代码的代码单元。你可以把一个对象当成一个名词:一个人,一个地方或事物。一个物体可以是一个精灵,一个绘图原始物或更像一个坦克在游戏中更精细的东西。具有一组特征和动作的具体实体可以表示为一个对象。

对象包含对象所需的数据以及对数据作用的方法(子例程和函数)。将数据和方法分组成单个实体称为封装。封装允许您创建可在多个程序中重复使用的模块化单元。代码重用的这个想法是创建OOP范式的主要动机。

封装的另一个有益的后果是隐藏信息。对象内的数据与外界屏蔽,不会对数据造成不必要的更改。而不是直接访问变量,该对象具有外部程序应用于访问和更改数据成员的公共接口。通过使用界面,您可以控制对象的行为方式,并确保其操作在许多程序中保持一致。

该界面还允许您对代码进行内部更改,而不会更改对象的访问方式。只要您不更改已发布的界面,那就是更改任何现有的公共方法,您可以改进对象,而不会破坏任何依赖对象的现有代码。在我需要一个改进方法的程序的情况下,您可以使用旧的方法来保持兼容性,并且只需添加一个改进的功能的新方法。新程序可以使用新方法,而旧程序仍然可以使用旧方法。

使用公共接口的另一个优点是,其他程序员可以使用您的对象,而不必担心对象的内部细节。只要发布的界面稳定,文档很好,任何人都应该能够使用你的对象,甚至是初学者。

发布合同


如前所述,OOP旨在实现程序员之间的代码重用。为了使代码重用有所帮助,已发布的界面必须保持稳定。也就是说,一旦一个对象被释放并被程序中使用,那么发布的界面不应该改变,所以使用对象的程序继续正常工作。作为对象的作者和对象的最终用户之间存在隐含的合同,您将在可能需要对对象进行的更改之间维护发布的界面。作者和用户之间的隐含契约是OOP范式的主要优势,也是OOP成为如此强大的编程方法的主要原因。

对象的特征


如前所述,一个对象包含数据和方法。数据描述对象的属性,而方法描述对象可以执行的操作。一个简单而非真正有用的例子将说明这一概念。

假设您要创建一个在屏幕上绘制矩形的对象。矩形可以包含几个属性,这些属性将包含在对象的数据成员中。矩形在屏幕上有一个原点,通常是左上角,可以由x和y数据成员表示。矩形具有宽度和高度,因此对象将具有宽度和高度数据成员。可以勾画或填充矩形,因此可以将填充的标志数据成员添加到对象。当然,如果要绘制一个矩形,您将需要以特定的颜色绘制它,因此该对象将需要一个颜色数据成员,并使对象更加灵活,您可以添加一个轮廓的颜色成员和填充的不同颜色成员。当然,您将需要一种方法来在屏幕上实际绘制矩形,因此您可以向对象定义添加绘图例程。

所以我们的矩形对象具有以下初步的属性和方法:

属性:x和y来源
属性:宽
属性:高度
财产:填补
属性:轮廓颜色
属性:填色
方法:DrawRect

此列表称为对象定义。在FreeBASIC中,使用扩展类型定义定义一个对象。扩展类型类似于标准类型,具有实现OOP功能子集的一些添加语言结构。

矩形类型定义


以下代码片段是部分矩形定义:
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:keywords和子声明。Private:关键字告诉编译器,后面的数据成员对类型是私有的,不能在类型之外访问。私有状态扩展到所有对象成员,直到遇到一个新的限定符,在这种情况下,它是Sub声明上方的Public:限定符。所有的数据成员都是从外部世界隐藏的,不能从类型的范围之外被改变,一个称为信息隐藏的过程。私有变量附加??的下划线是定义私有变量的常用方式。

信息隐藏是保持对象完整性的一种方式。您不应该允许外部进程直接访问数据成员。所有数据访问应通过使用属性成员,以便您可以控制传递给对象的内容。严格控制对象的数据将有助于防止程序员使用对象时可能发生的许多错误。
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

Public:限定符之后的Declare语句包含对象的公共接口。由于类型的变量使用Private:关键字定义,访问变量的唯一方法是通过Property成员维护对象的完整性。由于您在每个Property成员中定义代码,因此您可以完全控制要放入对象的内容。一个常见的例子是将范围检查代码放在属性成员中,以使对象不包含无效数据。

在这个例子中,变量可以被写入和读取。编译器根据方法的类型区分读属性和写属性。子程序格式的属性是一个写属性,因为您传递一个将保存在私有变量中的值。函数格式的Property是一个read属性,因为私有变量将被返回给调用者。您可以通过仅添加一个函数格式的Property或只写属性来创建只读属性,方法是添加一个子程序格式的Property。

创造良好的对象


这个定义看起来完整,但是有一个问题。如果部分或全部变量未初始化,会发生什么?该对象将无法正常执行,并可能会生成运行时错误。如果一个或多个变量未初始化,最好为对象变量设置一组默认值。您可以使用构造函数在创建时初始化对象。

构造函数是使用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

你会在定义中注意到我们有两个构造函数,一个是一组参数,一个没有。这称为重载,不仅可以与构造函数一起使用,还可以与其他子例程和函数一起使用。重载对于需要通过单一方法调用处理不同参数类型的情况非常有用。编译器将根据传递给该方法的参数确定要调用的方法。只要所有方法的参数的数量和类型都是唯一的,您可以按需要重载尽可能多的方法。

在这种情况下,如果构造函数未传递任何参数值,它将初始化变量到一组默认值。如果使用参数调用构造函数,那么它将使用传递的值来初始化对象的变量。

还有一个Destructor方法,当对象被破坏时被调用。您可以使用析构函数执行在从内存释放对象之前必须执行的任何清理任务。如果对象创建了任何指针引用,或者打开任何文件,那么您将清理析构函数中的这些引用。由于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 methodname.TypeName语法定义。这告诉编译器如何使用正确的类型定义来匹配方法。由于相同的原因,构造函数被定义为类型名称。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

'Create a graphic screen
Screen 18

'Create an object using the default constrcutor
Dim aRect As myRect
'Create an object by explicitly setting the constructor values
Dim bRect As myRect = myRect(200, 200, 200, 100, 1, 15, 9)

'Draw the rectangles on the screen
aRect.DrawRect
bRect.DrawRect

'Update aRect properties
aRect.X = 90
aRect.Y = 20
aRect.Filled = 1
aRect.FillColor = 15

'Draw new rect
aRect.DrawRect
Sleep
End


要使用默认的构造函数初始化对象,只需像标准类型一样调整扩展类型。如果构造函数只使用一个值,那么可以使用Dim var作为Typename = value语法。要使用一组值初始化对象,可以使用Dim类型,然后使用= typename(par1m parm1 ...)语法。您可以看到访问对象的成员就像访问标准类型的成员一样。

感谢在FreeBASIC论坛的cha0s有关属性的信息。