PowerBuilder 面向对象

来源:互联网 发布:激光打标软件常见问题 编辑:程序博客网 时间:2024/04/28 03:30

PB面向对象编程研究

1. 前言

众所周知,PowerBuilder是第四代面向对象开发语言。

面向对象程序设计的根本目的是实现数据的封装和隐藏。面向对象主要有三大特点,即封装性、继承性和多态性。

面向对象的封装性将数据与函数组织在同一个结构单元中,从而,实现数据和函数的关联以及数据对结构单元外的隐藏。进而实现结构单元与结构单元之间的弱耦合性。这里所说的结构单元就是类。

面向对象的继承性可以理解为类之间的遗传和变异。子类具有父类的性质同时又有区别于父类的性质,无论是属性还是方法。

面向对象的多态性实现的根本基础是类继承性背后的ISA关系。一个基类以及其所有派生类之间都存在ISA关系,他们的类型都属于基类类型。在实际使用中,根据对象实际的类型程序可以实现动态的方法调用。

本文主要叙述PowerBuilder面向对象的实现和特性。

2. 封装性

2.1. 对象

类,在PowerBuilder集成开发环境中,被称为对象(Object)。在编程过程中,我们所定义的对象变量被称为该对象的一个实例(Instance)。本文按照PB的习惯将类称为对象,将类的变量称为实例。

所谓的对象,我们可以认为是世间万物中任何一样东西,这件东西具有这样或者那样的特性,同时,这件东西还具有各种不同的行为。所谓的特性和行为就是我们在开发时我们所定义的数据和处理数据的函数。在面向对象的设计中,数据成员又称为属性,函数又称为方法。

2.2. 属性

属性即对象的特性,是编程过程中我们要处理的数据。在PB中,属性的表现形式为对象的实例变量(instance variable)。属性一般有三种访问权限(access right),即:

Public:公共

Protected:保护

Private:私有

公共访问权限指不仅对象内部方法可以直接访问该属性,对象外部的代码也可以直接该属性,就像我们在C/C++中使用结构体数据成员一样;保护的访问权限指对于外部代码该属性是受保护的,不允许访问,对于对象本身以及对象的后续继承对象都是公开的,可以直接访问该属性;私有访问属性是指不仅不允许外部代码访问该属性,该对象的后续继承对象也不能访问该属性,该属性为本对象的私有数据,只有对象本身可以访问。从这三种访问权限来看,似乎我们不应该定义属性的Public访问权限,因为,它违反了数据的封装和隐藏特性,我们应该定义Protected或者Private访问权限的属性,为了满足外部对特性的访问,我们只需要为该属性实现特定的访问方法。

下面几小节通过具体例子来体会一下属性访问以及初始化。

2.2.1. 访问权限

首先,我们演示在对象外部访问各种不同权限的属性。具体演示步骤如下图。我们创建一个uo_test自定义类的对象。

在uo_test的变量声明窗口中声明如下图变量。

接下来,我们在Application的open事件中创建一个uo_test对象uo_test_1的实例,然后分别访问三个实例变量。如下图。

保存时PB提示我们发现错误:没有足够权限访问类uo_test的属性。

其次,我们演示在对象的派生对象中访问不同权限的属性。由于目前还没有涉及到如何实现继承,所以以下演示省略了派生的过程。

在下图中,我们可以从标题看出uo_test_inherited是从uo_test继承过来的。

在这个演示部分,我们发现,PB在Private访问权限上与C++有所不同,虽然,PB禁止在派生类中访问Private权限的属性,但是PB的错误提示是:没有定义的变量。那么,是不是我们可以在派生类中定一个和基类中一样的属性ii_private呢?答案是否定的,PB认为在派生类中定义的这个试图覆盖基类的Private属性的变量在名字上是冲突的。如下图所示。

看上去很令人奇怪,提示没有定义,定义了又提示冲突,有些矛盾!而C++中的提示很明确,

如下图所示:

2.2.2. 可视初始化

创建一个窗口,将uo_test拖放到窗口中,查看non-visual object list窗口中uo_test_1的属性,可以看到ii_public和ii_protected两个属性。这足以证明PB将实例变量作为面向对象的属性来使用的。因此,我们在PB中可以实现在可视化属性窗口中实现对象的属性初始化。如下图。

可能有些朋友会有疑问,为什么没有ii_private?大家可以从属性窗口的标题看出来,uo_test_1是从uo_test继承而来,所以,uo_test_1是不能访问ii_private属性的。所以,可视初始化仅针对Public和Protected访问权限的属性而言。

2.2.3. 私有属性初始化

那么私有属性该怎么初始化,那就是在基类中。

2.3. 方法

方法即对象的行为,是用来处理对象属性的函数。方法与属性一样,具有三种访问权限:Public、Protected、Private。方法可以有参数也可以没有参数,可以有返回值也可以没有返回值。

2.3.1. 参数

在uo_test对象中添加一个方法func,增加一个参数,如下图:

图中1是传递方式,2是参数类型,3是参数名。

传递方式有有三种,如下图。

value表示传值方式,reference表示引用方式,readonly表示只读方式。一个好的方法设计,应该根据方法的实际情况选择参数的传递方式。选择value方式:当参数仅为传入的时候,但是在方法运行过程中需要改变该参数,例如传入一个参数表示方法中某个循环次数,修改参数做递减;选择reference方式:当参数为传出的时候,例如,方法利用该类型的参数作为方法的部分返回信息;选择readonly方式:参数仅为传入,并且在方法体内不做任何变化。选择合适的传值方式有助于正确编写和使用对象的方法(适用于普遍函数),即,从传值方式我们就能够知道该参数大致的操作性质,从而正确编写和使用。对于用户自定义类型比如结构体和类等的聚集类型,为了提高程序的性能,不建议使用value的传值方式。

参数类型有很多,点击右边的下拉键头就可以看到。同时,这个类型也可以使用用户自定义的数据类型,包括结构体、类等等,只需要将类型名字填入“Argument Type”即可。

参数名是方法体内使用的变量名,该变量通常称为形式参数。有时候,我们需要传递一个数组到方法中,那么我们可以在定义变量时在变量名后加上“[]”表示定义的参数为数组类型。在调用的过程中,将数组的名字传入即可。有时我们会看到有些人在调用这样的方法时将“[]”一并传入,从我的经验来说,这种调用方法有时会导致程序非法,所以,我个人不主张在调用方法是传入“[]”。

2.3.2. 返回值

选择合适的返回值类型。点击“Return Type”的下拉键头,我们可以选择需要的返回值类型,当然,我们也可以选择none(没有返回值)!当我们需要返回自定义数据类型时,可以将类型名称填入“Return Type”,方法就可以返回我们自定义的类型了!

2.3.3. 方法的调用

方法的调用也是有权限的,这一点和属性的权限类似。也分为:Public、Protected、Private三种。Public权限:类外部可以调用,类的子类也可以调用;Protected权限:类的外部不可以调用,类的子类可以调用;Private权限:类的外部部可以调用,类的子类也不可以调用,仅类的本身可以调用。

一个好的设计,应该选择合适的方法访问权限,有助于方法的正确使用,对于没有必要提供给外界的接口,要隐藏到类中。

创建func函数形如:

2.4. 事件

事件,PB将事件作为一个特性引入了面向对象编程的概念,指出一个对象在某种情况下应该做出什么样的反应和处理。例如,一个人在挨了另一个人一巴掌后应该怎么做?当然,人挨巴掌的时候是不能确定的,这一点正是事件的最根本特性,发生的不确定性。究竟在挨巴掌后是给予反击还是退缩就要看这个事件处理程序如何编写了。

在PB中最典型的两个事件就是构造器和析构器。构造器在这一点与C++编程语言有着本质的不同。虽然C++中的构造函数都是被系统在一定时机调用(类似事件),但是,C++中可以编写各种各样特定的构造函数,而PB中为规定好的接口样式的构造器事件。

2.4.1. 构造器

构造器事件在对象创建的瞬间被调用,其功能是初始化对象属性,以及对象被创建后需要执行的方法。

2.4.2. 析构器

析构器事件在对象被销毁的瞬间被调用,其功能是释放对象使用的资源等的收尾工作。

PB面向对象编程研究

1. 前言

众所周知,PowerBuilder是第四代面向对象开发语言。

面向对象程序设计的根本目的是实现数据的封装和隐藏。面向对象主要有三大特点,即封装性、继承性和多态性。

面向对象的封装性将数据与函数组织在同一个结构单元中,从而,实现数据和函数的关联以及数据对结构单元外的隐藏。进而实现结构单元与结构单元之间的弱耦合性。这里所说的结构单元就是类。

面向对象的继承性可以理解为类之间的遗传和变异。子类具有父类的性质同时又有区别于父类的性质,无论是属性还是方法。

面向对象的多态性实现的根本基础是类继承性背后的ISA关系。一个基类以及其所有派生类之间都存在ISA关系,他们的类型都属于基类类型。在实际使用中,根据对象实际的类型程序可以实现动态的方法调用。

本文主要叙述PowerBuilder面向对象的实现和特性。

2. 封装性

2.1. 对象

类,在PowerBuilder集成开发环境中,被称为对象(Object)。在编程过程中,我们所定义的对象变量被称为该对象的一个实例(Instance)。本文按照PB的习惯将类称为对象,将类的变量称为实例。

所谓的对象,我们可以认为是世间万物中任何一样东西,这件东西具有这样或者那样的特性,同时,这件东西还具有各种不同的行为。所谓的特性和行为就是我们在开发时我们所定义的数据和处理数据的函数。在面向对象的设计中,数据成员又称为属性,函数又称为方法。

2.2. 属性

属性即对象的特性,是编程过程中我们要处理的数据。在PB中,属性的表现形式为对象的实例变量(instance variable)。属性一般有三种访问权限(access right),即:

Public:公共

Protected:保护

Private:私有

公共访问权限指不仅对象内部方法可以直接访问该属性,对象外部的代码也可以直接该属性,就像我们在C/C++中使用结构体数据成员一样;保护的访问权限指对于外部代码该属性是受保护的,不允许访问,对于对象本身以及对象的后续继承对象都是公开的,可以直接访问该属性;私有访问属性是指不仅不允许外部代码访问该属性,该对象的后续继承对象也不能访问该属性,该属性为本对象的私有数据,只有对象本身可以访问。从这三种访问权限来看,似乎我们不应该定义属性的Public访问权限,因为,它违反了数据的封装和隐藏特性,我们应该定义Protected或者Private访问权限的属性,为了满足外部对特性的访问,我们只需要为该属性实现特定的访问方法。

下面几小节通过具体例子来体会一下属性访问以及初始化。

2.2.1. 访问权限

首先,我们演示在对象外部访问各种不同权限的属性。具体演示步骤如下图。我们创建一个uo_test自定义类的对象。

在uo_test的变量声明窗口中声明如下图变量。

接下来,我们在Application的open事件中创建一个uo_test对象uo_test_1的实例,然后分别访问三个实例变量。如下图。

保存时PB提示我们发现错误:没有足够权限访问类uo_test的属性。

其次,我们演示在对象的派生对象中访问不同权限的属性。由于目前还没有涉及到如何实现继承,所以以下演示省略了派生的过程。

在下图中,我们可以从标题看出uo_test_inherited是从uo_test继承过来的。

在这个演示部分,我们发现,PB在Private访问权限上与C++有所不同,虽然,PB禁止在派生类中访问Private权限的属性,但是PB的错误提示是:没有定义的变量。那么,是不是我们可以在派生类中定一个和基类中一样的属性ii_private呢?答案是否定的,PB认为在派生类中定义的这个试图覆盖基类的Private属性的变量在名字上是冲突的。如下图所示。

看上去很令人奇怪,提示没有定义,定义了又提示冲突,有些矛盾!而C++中的提示很明确,

如下图所示:

2.2.2. 可视初始化

创建一个窗口,将uo_test拖放到窗口中,查看non-visual object list窗口中uo_test_1的属性,可以看到ii_public和ii_protected两个属性。这足以证明PB将实例变量作为面向对象的属性来使用的。因此,我们在PB中可以实现在可视化属性窗口中实现对象的属性初始化。如下图。

可能有些朋友会有疑问,为什么没有ii_private?大家可以从属性窗口的标题看出来,uo_test_1是从uo_test继承而来,所以,uo_test_1是不能访问ii_private属性的。所以,可视初始化仅针对Public和Protected访问权限的属性而言。

2.2.3. 私有属性初始化

那么私有属性该怎么初始化,那就是在基类中。

2.3. 方法

方法即对象的行为,是用来处理对象属性的函数。方法与属性一样,具有三种访问权限:Public、Protected、Private。方法可以有参数也可以没有参数,可以有返回值也可以没有返回值。

2.3.1. 参数

在uo_test对象中添加一个方法func,增加一个参数,如下图:

图中1是传递方式,2是参数类型,3是参数名。

传递方式有有三种,如下图。

value表示传值方式,reference表示引用方式,readonly表示只读方式。一个好的方法设计,应该根据方法的实际情况选择参数的传递方式。选择value方式:当参数仅为传入的时候,但是在方法运行过程中需要改变该参数,例如传入一个参数表示方法中某个循环次数,修改参数做递减;选择reference方式:当参数为传出的时候,例如,方法利用该类型的参数作为方法的部分返回信息;选择readonly方式:参数仅为传入,并且在方法体内不做任何变化。选择合适的传值方式有助于正确编写和使用对象的方法(适用于普遍函数),即,从传值方式我们就能够知道该参数大致的操作性质,从而正确编写和使用。对于用户自定义类型比如结构体和类等的聚集类型,为了提高程序的性能,不建议使用value的传值方式。

参数类型有很多,点击右边的下拉键头就可以看到。同时,这个类型也可以使用用户自定义的数据类型,包括结构体、类等等,只需要将类型名字填入“Argument Type”即可。

参数名是方法体内使用的变量名,该变量通常称为形式参数。有时候,我们需要传递一个数组到方法中,那么我们可以在定义变量时在变量名后加上“[]”表示定义的参数为数组类型。在调用的过程中,将数组的名字传入即可。有时我们会看到有些人在调用这样的方法时将“[]”一并传入,从我的经验来说,这种调用方法有时会导致程序非法,所以,我个人不主张在调用方法是传入“[]”。

2.3.2. 返回值

选择合适的返回值类型。点击“Return Type”的下拉键头,我们可以选择需要的返回值类型,当然,我们也可以选择none(没有返回值)!当我们需要返回自定义数据类型时,可以将类型名称填入“Return Type”,方法就可以返回我们自定义的类型了!

2.3.3. 方法的调用

方法的调用也是有权限的,这一点和属性的权限类似。也分为:Public、Protected、Private三种。Public权限:类外部可以调用,类的子类也可以调用;Protected权限:类的外部不可以调用,类的子类可以调用;Private权限:类的外部部可以调用,类的子类也不可以调用,仅类的本身可以调用。

一个好的设计,应该选择合适的方法访问权限,有助于方法的正确使用,对于没有必要提供给外界的接口,要隐藏到类中。

创建func函数形如:

2.4. 事件

事件,PB将事件作为一个特性引入了面向对象编程的概念,指出一个对象在某种情况下应该做出什么样的反应和处理。例如,一个人在挨了另一个人一巴掌后应该怎么做?当然,人挨巴掌的时候是不能确定的,这一点正是事件的最根本特性,发生的不确定性。究竟在挨巴掌后是给予反击还是退缩就要看这个事件处理程序如何编写了。

在PB中最典型的两个事件就是构造器和析构器。构造器在这一点与C++编程语言有着本质的不同。虽然C++中的构造函数都是被系统在一定时机调用(类似事件),但是,C++中可以编写各种各样特定的构造函数,而PB中为规定好的接口样式的构造器事件。

2.4.1. 构造器

构造器事件在对象创建的瞬间被调用,其功能是初始化对象属性,以及对象被创建后需要执行的方法。

2.4.2. 析构器

析构器事件在对象被销毁的瞬间被调用,其功能是释放对象使用的资源等的收尾工作。

 

4. 多态性

重载不是面向对象的专有技术。比如,全局的函数也有重载的概念。但是相比之下,重载在类以及类的继承中的应用要比全局函数的重载更加频繁和重要。

4.1. 重载

重载是实现多态的基础,多态由重载实现,但重载并不一定实现的都是多态。重载有两种,一种是子类对父类同名同参数函数的重载,即实现多态;第二种是,在同类或者子类中同名不同参数的函数重载,姑且称为类中的普通重载。

4.1.1. 重载实现多态

第一步,我们在uo_test类的基础上,继承uo_test类创建uo_test_inherited类,如果按照前面的步骤,该类应该已经存在;

第二步,我们打开uo_test_inherited类,选择Function中的func函数,如下面的图中红色标记;

第三步,添加如上图的代码。

至此,我们完成了对父类uo_test的func方法在uo_test_inherited中的重载,下面将实现在程序中的调用,以演示func在类中的多态性。

在演示之前,为了明确究竟是调用的那一个类的func方法,我们将先前在uo_test中定义的func中的代码更改为下图内容:

下面开始正式演示PB中函数的多态性。

第一步,建立一个名为w_main_test的窗口,在窗口中添加函数polymorphism函数,如下图:

第二步,在w_main_test的open事件中添加如下代码,其中iu_test和iu_test_inherited分别是uo_test和uo_test_inherited的实例变量。如图:

创建对象后不要忘记销毁对象,本例中在w_main_test的close事件中进行的销毁,这里不作介绍。

第三步,运行程序,观察发生的情况:

我们发现,连续弹出两次“I am func of uo_test_inherited”的MessageBox窗口。

从运行结果我们可以知道,虽然定义的变量iu_test的类型是uo_test,但是创建的对象实体是uo_test_inherited类型,根据多态性,func应该调用的是uo_test_inherited类型中的func方法;在polymorphism函数中,虽然参数类型定义的是uo_test的类型,但是传入的参数实际上是uo_test_inherited类型,所以调用的依然是uo_test_inherited的func函数。

从上面的步骤中我们发现,我们仅仅是在uo_test_inherited类中重载了uo_test类中的func(integer ai_rtn)方法,我们并没有额外的做什么工作,不同于C++中,我们需要将成员函数声明为virtual类型。所以,在PB中每一个函数都可以在类的继承中重载实现多态,仅仅是重载。

4.1.2. 类中的普通重载

重载不仅仅是同名函数相同参数的实现多态方式的重载,也可以是同名函数不同参数的普通重载。也许,一直强调的“同名函数”似乎是多余的,因为,当函数名不同时也就无所谓重载了,不过,为了清晰强调一下也是必要的。

普通重载比实现多态的重载更加直接和简单。如下图,我们在uo_test_inherited类中增加一个func()的无参数版本。

同样,在w_main_test窗口的open事件中调用,看看实际结果如何,如下图:

我们发现,虽然iu_test的创建的实际类型是uo_test_inherited,但是,在调用func()时编译器却提示参数个数不对,这个进一步证明了面向对象中的多态性,无论创建或者传入的参数是什么类型,对象的类型依然依据声明时的类型,但是传入的类型可以影响被重载函数(在C++中被称为虚拟函数)的调用。

我们去掉错误的代码,运行,然后观察结果:

func()无参数版被正确调用了。函数名被成功的重载了,利用不同的参数,我们可以实现不同的处理了。

4.2. 动态调用

动态调用是PB区别于其他面向对象语言的一个特性,例如C++。动态调用与多态类似,或者我们可以称其为增强的多态性。动态调用究竟是什么,下面便研究便揭开谜底。

在4.1.2节中,使用iu_test调用func()无参数版本时发生了编译错误,因为uo_test类型中没有func()的无参数版本,但是事实上,iu_test的实例是通过uo_test_inherited类而创建的,在这个类中确实存在这个func()的无参数版本。如果实现多态性必须在每一个类中都要声明一个func()函数,而有时在某些父类中该函数不一定必须存在,而又需要该父类作为一个载体调用该函数该如何解决呢?面对这个问题,我们针对动态调用做个试验。

第一步,在w_main_test窗口中将iu_test.func()的代码更改为iu_test.dynamic func(),保存,看看编译器的反应,如下图:

编译器接受了,虽然uo_test类中不存在这个函数。

第二步,运行程序,看看实际结果如何:

连续出现了两个func()无参数版本的Messagebox窗口,这说明,通过Dynamic关键字实现了不需要每一个类都定义同名、同参数的函数来实现函数的多态性!这是PB对多态性的一个增强。

但是我更愿意说,这是对PB编译器的一个恶意欺骗。我们可以想象一下,如果写出这样的代码会怎样:iu_test.dynamic abc()?答案是什么,试试就知道!

在PB中,所有的对象均继承于powerobject对象,那么,我们下面修改一下w_main_test窗口的polymorphism函数的参数试一下,看看有什么结果。修改后的函数如下图:

此时,au_test已经是PowerObject对象实例了,本身已经不具备func(integer ai_rtn)有参数版本的函数了,但是编译器依然接受这样的代码,因为我们使用了动态调用关键字Dynamic关键字。

运行程序,我们发现与先前的测试没什么两样,依然是连续两次弹出如下图的MessageBox窗口:

说明我们的修改并没有对程序的运行结果造成什么影响。

动态调用给我们提供了更灵活的工具,我们可以在我们自己编写的类中加入同样的函数,而这个函数对于不同的类却有不同的操作,使用动态调用的功能,实现更普遍的代码。

有利也有弊,由于使用Dynamic关键字,使编译器不再检查该调用的有效性,或者说,编译器根本无法检查该调用在运行时是否正确,就像上面的例子中调用abc()函数一样。造成的结果便是系统的非法中断,而抛出系统异常。

5. 总结

本文的知识点不是甚多,总结如下:

1、 属性,类中的数据,类的特性,实现数据的封装与隐藏

2、 方法,类的成员函数,类的行为,实现对属性的操作

3、 属性与方法都具有访问权限

4、 对于方法的参数,选择合适的传递类型

5、 在构造事件中实现属性的初始化和资源的申请

6、 在析构事件中实现资源的释放,如:destroy iu_test

7、 继承类的构造与析构顺序都是先父后子

8、 类的继承性与类的多态性相互关联,继承性是多态性的基础

9、 父类的每一个函数都可以被子类重载,在调用时均可以实现多态调用

10、 没有在父类定义的函数,可以使用Dynamic关键字实现多态调用

11、 动态调用要求程序员必须清楚运行时的每一种可能

0 0