Clipper 教程:开源 Clipper(s)/面向对象编程指南
Clipper 对 OOP(面向对象编程,从现在开始我们称之为它的昵称)的支持非常有限。它的继任者 CA-Visual Objects 在这方面要先进得多。然而,Visual Objects 从未取得过巨大成功,第三方生产商为 Clipper 提供了 OOP 库,其中最著名的是 Class(y)、TopClass、Fivewin 和 Clip4Win。
假设我们正在处理给定点的笛卡尔坐标的距离函数 (http://mathinsight.org/cartesian_coordinates)。我们将应用的公式是
分别用于实数线、欧几里得平面和欧几里得空间上的(欧几里得)距离。
在过程式编程中,我们的函数看起来像这样
? distance1d(4,-3)
? distance2d(2,-3,-1,-2)
? distance3d(1,1,1,4,4,4)
FUNCTION distance1d( x1, x2 )
RETURN sqrt((x2-x1)^2)
FUNCTION distance2d( x1,y1,x2,y2 )
RETURN sqrt((x2-x1)^2+(y2-y1)^2)
FUNCTION distance3d( x1,y1,z1,x2,y2,z2 )
RETURN sqrt((x1-x2)^2+(y1-y2)^2+(z1-z2)^2)
我们定义了三个函数,具有不同的名称,它们以坐标作为参数。但这样做,我们需要为三维空间中的距离传递六个参数。
如果我们使用面向对象编程来做,我们可能会得到这样的东西
#include "hbclass.ch"
CREATE CLASS Point1D
VAR Abscissa // the abscissa of our point
METHOD New( Abscissa ) // Constructor
METHOD Distance( Point )
ENDCLASS
CREATE CLASS Point2D INHERIT Point1D
VAR Ordinate // the ordinate of our point
METHOD New( Abscissa, Ordinate ) // Constructor
METHOD Distance( Point )
ENDCLASS
CREATE CLASS Point3D INHERIT Point2D
VAR Zcoord // the Z-coordinate of our point
METHOD New( Zcoord ) // Constructor
METHOD Distance( Point )
ENDCLASS
&& Constructors Zone
METHOD New( Abscissa ) CLASS Point1D
::Abscissa := Abscissa
RETURN Self
METHOD New( Abscissa, Ordinate ) CLASS Point2D
::Abscissa := Abscissa
::Ordinate := Ordinate
RETURN Self
METHOD New( Abscissa, Ordinate, Zcoord ) CLASS Point3D
::Abscissa := Abscissa
::Ordinate := Ordinate
::Zcoord := Zcoord
RETURN Self
&&Distances Methods
METHOD Distance( Point ) CLASS Point1D
RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 )
METHOD Distance( Point ) CLASS Point2D
RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 )
METHOD Distance( Point ) CLASS Point3D
RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 + ( Self:Zcoord - Point:Zcoord ) ^ 2 )
PROCEDURE Main()
FirstPoint := Point1D():New( 3 )
SecondPoint := Point1D():New( - 3 )
? FirstPoint:Abscissa
? FirstPoint:Distance( SecondPoint )
ThirdPoint := Point2D():New( 2, - 3 )
FourthPoint := Point2D():New( - 1, - 2 )
? ThirdPoint:Distance( FourthPoint )
FifthPoint := Point3D():New( 1, 1, 1 )
SixthPoint := Point3D():New( 4, 4, 4 )
? FifthPoint:Distance( SixthPoint )
RETURN
在这里,我们定义了三个类,它们的构造函数,以及每个类的distance方法,并展示了如何使用它们。这也是继承如何工作的简单示例。其他概念是封装(信息隐藏或数据隐藏)、抽象、多态性、重载和覆盖方法。继承在关键概念:重用中起着核心作用。如果一个类恰好是所需的,那么它可以在软件项目中重用;或者,如果它不是完全需要的,可以通过定义一个子类来扩展它。就像数据库的设计一样,面向对象类的设计也是一门艺术,有其原则,例如参见http://www.oodesign.com/、http://www.codeproject.com/articles/567768/object-oriented-design-principles以及有关 UML 的页面,如http://www.uml-diagrams.org/uml-object-oriented-concepts.html。
首先要注意的是,我们从包含clipper 头文件hbclass.ch开始,这是 Class 命令的头文件。
对对象变量和方法的访问是通过冒号运算符完成的。前缀双冒号指的是具有更大范围的变量(例如传递给方法的变量)。
在上面的代码中,我们定义了三个类,每个类都实现了一个 Point。例如,Point2D 被定义为一个扩展 Point1D 的类,即概念的泛化。为每个类提供了一个 Distance 方法。
像这样的行
? FifthPoint:Distance( SixthPoint )
包含输出命令?,对一个对象的引用(在本例中为FifthPoint),对 Distance 方法:Distance的调用,并将另一个点传递给它( SixthPoint )。
也可以编写一个接受两个 Point 类参数的 Distance 函数,看起来像这样
FUNCTION Distance ( Point1, Point2 )
RETURN Sqrt( ( Point1:Abscissa - Point2:Abscissa ) ^ 2 + ( Point1:Ordinate - Point2:Ordinate ) ^ 2 + ( Point1:Zcoord - Point2:Zcoord ) ^ 2 )
? Distance( FifthPoint, SixthPoint )
然而,这不是面向对象编程,因为我们可以在非面向对象语言(如 Pascal 或 C)中使用相同的函数,并传递给它两个名为 Point1 和 Point2 的struct或record(Pascal 称之为)。
重要的是,对象内部的一些数据(由该对象的实际程序员设置)不能被对象用户更改。举一个现实生活中的例子,我们可以考虑汽车发动机。对象的提供者设置了气缸的数量,我们没有太多机会改变它:我们必须把它视为一个常数。自然地,关于发动机有很多有趣的公式供工程师使用(一些可以在http://www.thecartech.com/subjects/engine/engine_formulas.htm上看到)。例如,计算发动机容积效率的公式,给定进入气缸的空气量和气缸扫过的体积。这就是数据隐藏的重要性:没有人需要知道这些信息才能让他的汽车行驶。此外,当有人设计发动机时,他们可能不希望用户通过操作发动机来更改容积效率。在面向对象编程中,使用可见性修饰符或访问修饰符可以获得相同的效果。
[CREATE] CLASS <cClassName> [ FROM | INHERIT <cSuperClass1> [, ... ,<cSuperClassN>] ]
[ MODULE FRIENDLY ] [ STATIC ] [ FUNCTION <cFuncName> ]
[HIDDEN:]
[ CLASSDATA | CLASSVAR | CLASS VAR <DataName1>]
[ DATA | VAR <DataName1> [,<DataNameN>] [ AS <type> ] [ INIT <uValue> ]
[[EXPORTED | VISIBLE] | [PROTECTED] | [HIDDEN]] [READONLY | RO] ]
...
[ METHOD <MethodName>( [<params,...>] ) [CONSTRUCTOR] ]
[ METHOD <MethodName>( [<params,...>] ) INLINE <Code,...> ]
[ METHOD <MethodName>( [<params,...>] ) BLOCK <CodeBlock> ]
[ METHOD <MethodName>( [<params,...>] ) EXTERN <funcName>([<args,...>]) ]
[ METHOD <MethodName>( [<params,...>] ) SETGET ]
[ METHOD <MethodName>( [<params,...>] ) VIRTUAL ]
[ METHOD <MethodName>( [<params,...>] ) OPERATOR <op> ]
[ ERROR HANDLER <MethodName>( [<params,...>] ) ]
[ ON ERROR <MethodName>( [<params,...>] ) ]
...
[PROTECTED:]
...
[VISIBLE:]
[EXPORTED:]
...
[FRIEND CLASS <ClassName,...>]
[FRIEND FUNCTION <FuncName,...>]
[SYNC METHOD <cSyncMethod>]
ENDCLASS [ LOCK | LOCKED ]
从 w:Harbour (软件) 中逐字复制
#include "hbclass.ch"
PROCEDURE Main()
LOCAL oPerson
CLS
oPerson := Person():New( "Dave" )
oPerson:Eyes := "Invalid"
oPerson:Eyes := "Blue"
Alert( oPerson:Describe() )
RETURN
CREATE CLASS Person
VAR Name INIT ""
METHOD New( cName )
METHOD Describe()
ACCESS Eyes INLINE ::pvtEyes
ASSIGN Eyes( x ) INLINE iif( HB_ISSTRING( x ) .AND. x $ "Blue,Brown,Green", ::pvtEyes := x, Alert( "Invalid value" ) )
PROTECTED:
VAR pvtEyes
ENDCLASS
// Sample of normal Method definition
METHOD New( cName ) CLASS Person
::Name := cName
RETURN Self
METHOD Describe() CLASS Person
LOCAL cDescription
IF Empty( ::Name )
cDescription := "I have no name yet."
ELSE
cDescription := "My name is: " + ::Name + ";"
ENDIF
IF ! Empty( ::Eyes )
cDescription += "my eyes' color is: " + ::Eyes
ENDIF
RETURN cDescription