原贴地址:http://scnblogs.techweb.com.cn/abaplv/archives/127.html
几年前SAP BASIS 4.6为ABAP扩展了OO功能,这是很多传统的ABAP程序员陷入困境。首先对于ABAP程序员来说它们终于可以通过OO这种开发方法得潜在价值来提高 开发效率和所开发程序的可维护性了。然而一直以来习惯于面向过程编程的ABAPER迅速的转向OO模式也不是那么容易,很多人还是坚持使用传统的面向过程 的开发模式,他们觉得不用OO也可以得到他们想要的。这篇文章的主要目的便是让你明白OO能给你带来什么以及如何转型为OO模式。
很多关于ABAP OO方面的文章要么过于抽象,要么举的例子过于简单对于实际应用无关痛痒。本篇文章力图通过简单而又切合实际的例子通过面向过程和面向对象两种模式的比较来说明OO的优点。
1. 首先ABAP OO具有更高层次的数据封装性,从而增强了程序的可维护性和稳定性。在面向过程的模式里,一个程序的全局变量区包含着许多不相干的变量,这些变量在一个区 域里交织在一起,这样的话这个程序的某个功鞥对程序状态的改变并不为程序中另外一个功能所知。为保持整个程序的稳定性需要大量的约束和维护成本。而对于 OO模式,状态是保存在对象上的。对象可以把内部和外部数据和方法分隔开,以保证功能(OO中的方法)只能改变与它相关的数据,对象的属性是不会被改变 的,从而保证了其在应用程序中的稳定性。
2. ABAP OO可以实现一个类的多个实例。(对象是由数据以及操作数据的方法组成的。),每一个对象都有自己在类中定义的属性值,并且可以通过本身的方法来改变本身 的状态。这就意味着开发者无需为每个对象建立数据和方法之间的联系也无需手工管理每个对象的生命周期。在面向过程的方法中没有多实例的概念,数据和功能是 相互分离的。你使用的是无状态的功能,并且每次都需要通过传递参数来初始化它,并且手工将其占有的内存清除。
3. ABAP OBJECT通过继承进一步增强了程序代码的可重用性,这正是面向对象方法的一个重要方面。通过这个特点你可以重复利用所继承类的部分或者所有方法,只需 编写类本身所特有的方法,这样会减少为每个类编写的方法,从而增强了程序的可维护性。而对于面向过程的编程来说,你将会受制于all-or- nothing的状况,你要么调用所有的部分要么就建立新的。
4. ABAP OO是你可以通过接口(interface)来调用对象的业务逻辑,而不是直接去使用对象,这样就避免了你需要详细了解每一个对象的特定功能。这样也避免 了你在需要修改某项功能的时候无需修改接口(interface)的内容,这也为标准程序的修改提供了新的途径,即BADI。面向过程的编程并没有这种提 供对立的接口去联系某个对象的概念—接口只是通过form或function module的参数实现的。
5. ABAP OO非常容易与事件驱动的模式结合在一块。不同的应用之间是通过发布与预定松散的耦合在一起的,调用者和被调用者之间并不是被动的绑定在一起的。这种方式 与面向过程的方法相比更进一步的增强了灵活性,他们的绑定是紧密地,程序的执行流程也是预定义好的。
当然,这些好处都需要你今后再开发中是用OO的方法来开发才能得到。然而重新建模你应用程序的开发模式是令人沮丧和头疼的,我们保证这种情况不会出现,读 完这篇文章我们相信你会得到同样的结论。当然,没有说我们需要重新建模已有的应用,我们只是希望你在今后的开发过程中最后考虑一下OO的模式。
如果你坚持认为面向过程的模式对你来说已经足够了,也请接着往下读。通过使用对象的方法来代替form和function module你仍然可以增强你的ABAP程序。
1. ABAP OO更加明确所以更易于使用。例如在使用ABAP OO你的程序的执行流程不再是由运行时隐含的控制。这样你就可以自己去设计程序所执行的流程了而不必像面向过程那样去了解和服从外部控制机制(即报表和 dialog screen的事件)。
2. ABAP OO具有更加清晰的语法和语义规则,比如一些容易出错的过时的语句在ABAP OO类中已经明确不能再使用。而在面向过程的程序中这些语法仍然被支持,顶多就是在关键的时候给你报个警告信息。
3. ABAP的一些新技术只能通过ABAP OO来实现。例如所有新的GUI的概念比如SAP Control Framework和BSP只有通过ABAP OO的方式才能够实现。而对于面向过程的ABAP你就只能使用传统的screen和list processing了。
所以即便你在未来的开发中还不准备完全的转型为OO,你可以使用的OO技术来减少错误的隐患以及增强代码的可维护性。下面的部分将会阐述如何达到这一目的。
那么面向过程的ABAP和ABAP OO究竟是孰优孰劣?下面的部分将逐一进行论述。首先先了解以下ABAP OO的年代史。
1. SAP Basis Release 4.5发布了ABAP OO的一个版本,引入了类接口的概念,并可以通过类来创建对象(实例化类)。
2. SAP Basis Release 4.6发布了ABAP OO的完全版本,引入了OO方式的重要概念继承(inheritance),可以通过多个接口来建立一个复合的接口。
3. SAP WEB APPLICATION SERVER 6.10/6.20 SAP basis的下一代版本,在类之间引入了friendship的概念。并引入了对象服务(object service)可以把对象存储在数据库中。
4. SAP WEB APPLICATION SERVER 6.40引入了共享对象(Shared Objects)的概念,即允许在应用服务器的共享内存中存储对象。这样在这个服务器中的任何一个程序都可以访问它。
几个关键点
n ABAP OO是ABAP编程语言的扩展
n ABAP OO 是向下兼容的
n SAP发布ABAP OO是为了进一步增强代码的可重用性
n 随着ABAP OO的发布,ABAP运行时支持面向过程和面向对象两种模式
对于面向过程的模式,程序的运行通常是从 screen 的 dialog module 或 selection screen 的 start-of-selection 事件开始的。你在这些处理模块中操作全局变量来实现需求的功能。你可以通过内部的 form 和外部的 function module 来实现程序的模块化。这些过程除了可以操作全局变量外还可以具备内部的本地变量来协助实现内部的一些特定功能。
对于OO编程,唯一的结构单位就是类,这里类的实例对象取代了全局变量。这些对象封装了应用的状态和行为。应用的状态是用属性来代表的它取代了面向过程中的全局变量。应用的行为是通过方法来实现的,他们用来改变应用的属性或者调用其它对象的方法。 ABAP OO支持OO和面向过程的两种模式,这样在传统的ABAP程序(比如报表,模块池,功能池等)中你也可以使用ABAP对象类。在这些程序里你也就可以使用基于面向对象的新技术了,比如一些用户界面,避免了要想使用这些新技术必须重新编写程序。
![3](http://scnblogs.techweb.com.cn/abaplv/files/2009/08/3.gif)
虽然纯粹的OO模式技术上是可行的,但是现实中还存在着大量的两种模式的混合体如右面的图所示。ABAP 对象和面向过程的技术同时应用,调用常用的功能模块,调用屏幕或者直接访问数据库等在对象中都存在。混合的模式及利用了新技术又保护了已投入的成本。
两种模式的选择
正如本文所述,OO的模式是最佳的选择,除非在绝对必要的情况下才使用面向过程的模式。比如传统的screen programming在OO中是不支持的,附录中会进一步阐释如何实现screen与OO的结合。
下面的部分里我们将着重论述OO编程的主要优点,尽管这里所提到的优点与其他的OO语言(JAVA C++)没有什么太大的区别,我们这里着重在ABAP OO与传统的ABAP程序相比而体现处来的优点。我们将通过实现一个银行账户管理的简单例子来比较两种模式的差别。
原因一:数据封装
将数据和程序封装在一个组件中将使程序变得容易修改。不要把一个应用的所有的数据和功能放在同各超长的程序里,你只需要把你的应用通过组件的稳定的接口把 需要的部分包进来即可。如果一个封装的组件有一个好的接口,而这些接口的设计比较完善的话,那么这些接口的内部结构就会被很好的隐蔽起来,对这个部件的内 部的修改便不会影响到应用的其他部件。让我们来看看面向过程和面向对象的方式是如何实现封装的。
面向过程模式的封装
在面向过程模式中有两种变量:
全局变量:他在程序的全部分声明,可以在程序的仍何不分应用。
局部变量:他在某个过程(form of function module)中声明也只能在其中应用。
全局变量的生命周期取决于整个程序的生命周期,局部变量的生命周期取决于该过程的生命周期。由于生命周期的限制,局部变量不适合于封装,它是过程中为了达 到某个目的而使用的辅助变量。如果没有使用TABLES和COMMON PARTS语句那么程序中所声明的全局变量只能被该程序内部的各个模块调用。那么这就决定了在面向过程的模式中数据的封装只对程序本省有效。下面是一个简 单的如何通过功能池实现对一个银行帐号的封装。
Function-pool Account
DATA: current_account TYPE accounts-amount.
Function deposit.
* IMPORTING REFERENCE(amount) TYPE account-amount
Current_amount = current_amount + amount.
EndFunction.
Function withdraw.
* IMPORTING REFERENCE(amount) TYPE account-amount
* RAISING
* CK_NEGATIVE_AMOUNT
IF current_amount > amount.
Current_amount = current_amount – amount.
ELSE.
RAISE EXCEPTION TYPE CK_NEGATIVE_AMOUNT.
ENDIF.
ENDFUNCTION.
这个模块池封装了银行账户的余额,并且通过功能模块deposit和withdraw来处理银行账户的余额。虽然单单是一个银行帐号的话这个模块池工作的 非常好,但如果是多个应行帐号并要实现银行帐号之间交互的功能,问题就出现了。为了实现现实世界中多个银行帐号的管理,你不得不为每一个银行帐号建一个唯 一名字的模块池和功能模块,这显然是不可取的。下面是个把所有银行帐号封装在一个模块池中的例子:
FUNCTION-POOL accounts.
DATA: account_table TYPE SORTED TABLE OF accounts
WITH UNIQUE KEY id.
ENDFUNCTION.
LOAD-OF-PROGRAM.
SELECT * INTO TABLE account_table
FROM accounts.
FUNCTION deposit.
* IMPORTING
* REFERENCE(id) TYPE accounts-id
* REFERENCE(amount) TYPE accounts-amount
DATA: account_wa TYPE accounts.
READ TABLE account_tabe
INTO account_wa
WITH TABLE KEY id = id.
Account_wa-amount = account_wa-amount + amount.
MODIFY account_table FROM account_wa.
ENDFUNCTION.
FUNCTION withdraw.
* IMPORTING
* REFERENCE(id) TYPE accounts-id
* REFERENCE(amount) TYPE accounts-amount
* RAISE
* CX_NEGATIVE_AMOUNT
DATA: account_wa TYPE accounts.
READ TABLE account_table
INTO account_wa
WITH TABLE KEY id = id.
IF account-amount > amount.
Account-amount = account-amount – amount.
MODIFY account_table FROM account_wa.
ELSE.
RAISE EXCEPTION TYPE CX_NEGATIVE_AMOUNT.
ENDIF.
ENDFUNCTION.
FUNCTION transfer.
* IMPORTING
* REFERENCE(id_from) TYPE accounts-id
* REFERENCE(id_to) TYPE accounts-id
* REFERENCE(amount) TYPE accounts-amount
* RAISE
* CX_NEGATIVE_AMOUNT
CALL FUNCTION ‘withdraw’
EXPORTING id = id
Amount = amount.
CALL FUNCTION ‘deposit’
EXPORTING id = id
Amount = amount.
ENDFUNCTION.
这样多个银行帐号就被封装在内表account_tab中,每个银行帐号是通过关键字ID来区分的。内表account_tab在模块池被调用时被填充以 便模块池中的所有功能模块都可以使用它。功能模块deposit和withdraw通过id来处理一个银行帐号,而新的功能模块transer来实现两个 银行帐号之间的转账。这样我们通过一定的技巧实现了多个银行帐号的管理但是为了区别每个银行帐号却增加了每个功能模块接口参数的复杂性。
OO模式的编程也有两种数据类型,类实例的属性和类方法中的本地变量。
实例属性的生命周期依赖于对象的生命周期,本地变量的生命周期依赖于类方法的生命周期。所以面向对象的本地变量与面向过程的本地变量的生命周期是一样的。 它是类方法中的辅助变量与数据封装无关。ABAP对象的数据封装在类这个级别。可以定义类属性的可见性,它的级别分别是在类内可见,在子类中可见,或者在 外部类中可见。下面的例子演示了如何在类中封装银行账号:
CLASS account DEFINITION.
PUBLIC SECTION.
METHODS: constructor IMPORTING id TYPE account-id,
Deposit IMPORTING amount TYPE accounts-amount,
Withdraw IMPORTING amount TYPE accounts-amount
RAISING cx_negative_amount,,
Transfer IMPORTING amount TYPE accounts-amount
Target REF TO account
RAISING cx_negative_amount.
PRIVATE SECTION.
DATA amount TYPE accounts-amount.
ENDCLASS.
CLASS account IMPLEMENTATION.
METHOD constructor.
SELECT SINGLE amount INTO (amount)
FROM accounts
WHERE id = id.
ENDMETHOD.
METHOD deposit.
Me->amount = me->amount + amount.
ENDMETHOD.
METHOD withdraw.
IF me->amount > amount.
Me->amount = me->amount – amount.
ELSE.
RAISE EXCEPTION TYPE cx_negative_amount.
ENDIF.
ENDMETHOD.
METHOD transfer.
Me->withdraw( amount ).
Target->deposit( amount ).
ENDMETHOD.
ENDCLASS.
在account类的定义部分可以定义属性和方法的可见性(private public)。类的定义把功能和数据结合到了一块。理论上,前面例子的功能池也做到了这点,只不过在不同的数据模块间进行交互的时候就暴露了其局限性, 所以功能池只能说是多个功能模块的容器还不能真正的共享数据。在ABAP OO的类中PUBLIC部分定义了其与外部交互的接口。在这个例子中描述类状态的属性是私有的,而对外的公共接口是操作这些属性的方法。
这个例子中的类既实现了前面面向过程的第一个例子的简单功能也实现了第二个例子较为复杂的功能。这个类的deposit,withdraw方法与第一个例 子的两个功能模块的作用相同,由于你可以创建一个类的多个对象,所以这个类只需封装一个银行帐号的数据即可,constructor方法用来为不同的银行 账号初始化数据。由于每个对象都有它自己的数据,所以你不需为类添加额外的参数。最后,不止于此,由于一个对象可以调用另外一个对象的方法,那么不同银行 账号之间的交互便有了更加简洁的方案,这例子中transfer功能的实现要比前面第二个例子的实现简单的多。
对象的实例化是面向对象编程的一个重要特点。在ABAP OO中,通过实例化你可以实现一个类的多个变体,这样程序员便可以调用任何一个对象并让他完成相应的功能(比如一个银行帐号的收款存款和窗户的打开关闭等)。
在面向过程的编程中,当程序内容被编译到内存中便隐含着一个实例化的过程,但是这个实例并不能为程序员显式的调用,因为这需要数据和功能的分离,而面向过 程的的程序员并不是直接对对象进行操作,而是要告诉一个功能模块需要修改那些数据(比如需要告诉功能模块那个银行帐号要付款或者那个窗户要打开)。下面我 们将详细描述这两种不同的实例化之间的区别。
面向过程的实例化
大多数程序员可能没有意识到其实在面向过程的程序里也有类似于对象实例化的现象。程序本身在被调入内存的时候其实就是一个隐含的实例化过程,因为程序本身被调用或者程序中的某个过程被其他程序调用了。
模块池隐含实例化的例子
DATA: id1(8) TYPE n,
Id2(8) TYPE n,
Amnt TYPE p DECIMALS 2,
Exc_ref TYPE REF TO cx_negative_amount,
Text TYPE string.
TRY.
Id1 = …..
Id2 = …..
Amnt = …..
CALL FUNCTION ‘TRANSFER’
Id_from = id1
Id_to = id2
Amount = amnt.
CATCH cx_negative_amount INTO exc_ref.
Text = exc_ref->get_text().
MESSAGE text TYPE i.
ENDTRY.
上面的例子演示了调用银行账户模块池的转账功能模块的例子,两个银行账户是通过账户ID来区分的。
如果银行账户模块池中的功能模块是第一次被调用,那么整个模块池就会被调到调用程序的内部session中,模块池的LOAD-OF-PROGRAM事件 就会被触发。对应的事件部分充当着模块池实例化的结构事件。如果在同一个内部session中,在同一个程序或另一个程序中银行帐号模块池已经被调用,那 么下一调用,模块池已经被装载到这个内部session中。由于程序或者过程只有一次装载到内存的过程,所以功能模块便可以使用其所在模块池中的全局数 据。
这种实例化对于数据封装带来了如下缺陷:
l 你无法控制实例化的时刻(在一个较大的程序中功能模块在任何时候,任何地点都有可能被调用),这样当一个程序在调用功能模块的时候它将无法知道是否另一个程序或过程改变了模块池的全局数据。
l 你无法在内存中删除模块池的实例,因为模块池的实例的生命周期取决于调用主程序的生命周期。如果你想在模块池的全局数据中使用数据量较大的变量,那么你在 使用后需要手工将其清除。另外模块池一般是许多功能模块的容器,这样就会给内存带来很大的压力,即便是你只是在较短的时间调用很简单的功能模块。
l 每个程序在内存中只能创建一个实例。正如我们在原因1种所看到的,这就限制了我们充分发挥模块池的数据封装性。
还要注意的如果在模块池中,全局变量不能被整取操作,那么将是功能模块对全局变量的使用变得更加危险。全局变量的状态取决于功能模块的调用顺序,尤其是在一个大程序里,功能模块在不同的地方都有可能调用,这样就导致了模块池全局变量状态的不稳定。
行如其名,在面向对象的模式中,对象是唯一存在的东西。对象是类的实例,是通过CREATE OBJECT语句来实现的。ABAP对象不可能被隐含的实例化,你通过指针变量来操作对象。
当然你通过指针所操作的对象不一定是自己创建的,这种现象主要是通过指针操作的对象很有可能是从其他地方传过来的,比如说工厂方法(factory method)或者接口参数。你可以在一个类中创建多个对象每个对象通过属性都有自己的标识符和内容。
对象的生命周期是由使用者来控制的(其他对象,程序或过程等),只要有指针变量指向他,他就在内存中存在。
如果没有指针变量指向对象,这个对象就会被自动的被ABAP内存收集器从内存中删除。和程序的实例一样,ABAP对象也存储在主程序的内部SESSION 中。在640之后你就可以在应用服务器的SHARED MEMORY中创建shared object了,这样在这应用服务器上运行的任何程序都可以调用它。
下面的例子演示了从银行账号类中产生的两个银行账号对象。
DATA: account1 TYPE REF TO account,
Account2 TYPE REF TO account,
Amnt TYPE p DECIMALS 2,
Exc_ref TYPE REF TO cx_negative_amount,
Text TYPE string.
CREATE OBJECT : account1 EXPORTING id = ….,
Account2 EXPORTING id = …..
TRY.
Amnt = ….
Account1àtransfer ( EXPORTING amount = amnt
Target = account2).
CATCH cx_negative_amount INTO exc_ref.
Text = exc_ref->get_text().
MESSAGE text TYPE i.
ENDTRY.
指针变量account1,account2是类account的对象的显示的操作符。在CREATE OBJECT语句中每个账号标识被传入到了对象的结构体。对象一旦被创建,账号标识就不用再被声明了。你可以调用一个对象的transfer方法,通过通 过账号指针变量来指明要转入的账号。
与面向过程的上面的例子相比,显然面向对象的例子更加直接。在面向过程的例子里你不得不为每个功能模块指明要操作的数据,而在面向对象的例子里你只需调用 对象的方法那么他就会自动地使用它自己的数据。值得注意的是例外信息我们都是使用基于类的方法,这在610之后是被推荐的。在面向过程的程序中使用指针变 量exc_ref显得有些另类,因为在非OO模式的程序中使用了OO模式的东西,这也是ABAP OO的一个自然特性。
代码重用性是软件维护和开发的一个重要衡量指标。我们应当把一些通用的代码放在一个中心库里,而无须一遍一遍的重复编写功能相似的代码。下面就在代码重用方面检查一下ABAP OO和ABAP OP孰优孰劣。
面向过程的代码重用性
在面向过程的模式中没有什么专门的机制来支持代码的重用性。你可以定义通用的过程,但是如果你想在特殊情况下使用通用代码,你不得不建立很庞大的模块池,每个功能模块又不得不有复杂的参数,而每个功能模块中你又不得不使用复杂的分支语句,例如SWITCH CASE.。
比如为了给银行账号加入两个特殊的账号,检查账号和存款账号,我们不得不修改我们的程序以支持这种变化。下面的例子演示了对功能模块withdraw的修改。
FUNCTION withdraw.
CASE kind.
WHEN ‘C’.
PERFORM withdraw_from_checking_account USING id amount.
WHEN ‘S’.
PERORM withdraw_from_savings_account USING id amount.
WHEN OTHERS.
RAISE EXCEPTION TYPE cx_unknown_account_type.
ENDCASE.
ENDFUNCTION.
FORM withdraw_from_checking_account.
USING i_id TYPE accounts-id
Amount TYPE accounts-amount.
DATA: account_wa TYPE accounts.
READ TABLE account_tab WITH TABLE KEY id = i_id
INTO account_wa.
Account_wa-amount = account_wa-amount – amount.
MODIFY TABLE account_tab FROM account_wa.
IF account_wa-amount < 0.
………..
ENDIF.
ENDFORM.
FORM withdraw_from_savings_account.
USING i_id TYPE accounts-id
Amount TYPE accounts-amount
RAISING cx_negative_amount..
DATA: account_wa TYPE accounts.
READ TABLE account_tab WITH TABLE KEY id = i_id
INTO account_wa.
IF account_wa-amount > i_amount.
Account_wa-amount = account_wa-amount – amount.
MODIFY account_tab FROM account_wa.
ELSE.
RAISE cx_negative_amount.
ENDIF.
ENDFORM.
这样你就不得不怎加新的参数帐户类型了
在这个例子中还得增加新的例外信息,即如果帐户类型不在合理范围之内的情况。
最终为了实现对不同的帐户类型采取不同的处理逻辑,那么你就不得不写两个form来分别处理两种不同的情况。通过CASE语句来实现分支。
ABAP OO的代码可重用性
在ABAP OO中通过继承来实现代码的可重用性,可以通过通用的父类来派生特殊的子类。这样好处是简单的类既可代替代码庞杂的模块池,简单的方法参数既可代替功能模 块复杂的参数,最重要的是通过继承实现了类的多态。这样就实现了子类的对象仍然可以使用父类的方法。这大大增强了软件开发的建模能力。
CLASS account DEFINITION.
PUBLIC SETION.
PROTECTED SETION.
DATA amount TYPE accounts-amount.
ENDCLASS.
CLASS account IMPLEMENTATION.
METHOD withdraw.
ME->amount = ME->amount – amount.
ENDMETHOD.
ENDCLASS.
正如你所见属性amount已经由private改为protected以便它的两个子类也可以放问它。方法withdraw已经被通用化,它不再抛出异常。这些改变外界不会察觉到,这也是实现多态的前提条件。父类account的子类checking account
CLASS checking_account DEFINITION
INHERIATING FROM account.
PUBLIC SECTION.
METHODS withdraw REDEFINITION.
ENDCLASS.
CLASS checking_account IMPLEMENTATION.
METHOD withdraw.
Super->withdraw ( amount ).
IF me->amount < 0.
…..check debit amount
ENDIF.
ENDMETHOD.
ENDCLASS.
父类的子类savings_account
CLASS savings_account DEFINITION
INHERIATING FROM account.
PUBLIC SECTION.
METHOD withdraw REDEFINITION.
ENDCLASS.
CLASS savings_account IMPLEMENTATION.
METHOD withdraw.
IF me->amount > amount.
Me->amount = me->amount – amount.
ELSE.
RAISE EXCEPTION TYPE cx_negative_amount.
ENDIF.
ENDMETHOD.
ENDCLASS.
通过INHERIATING FROM使得父类和子类有着相同的属性和参数。通过方法withdraw REDEFINITION来重新编写子类的同名方法。通过super->可以调用父类的方法,在两子类中这两个withdraw方法根据不同的帐户 类型作了改动,例如在这两个子类中分别对当帐户余额为负数是抛出异常或者贷向处理。
下面的例子演示了一个父类,两个子类的使用:
DATA: account1 TYPE REF TO account,
Account2 TYPE REF TO account,
Amnt TYPE p DECIMALS 2,
Exec_ref TYPE REF TO cx_negative_amount,
Text TYPE string.
CREATE OBJCET account1 TYPE checking_account
EXPORTING id = ..
CREATE OBJCET account2 TYPE savings_account
EXPORTING id = …
TRY.
Amnt = …
Account1->transfer( EXPORTING amount = amnt
Target = account2 ).
CATCH cx_negative_amount INTO exec_ref.
Text = exec_ref->get_text().
MESSAGE text TYPE i.
ENDTRY.
与前面的面向对象的例子相比,唯一的不同便是在创建对象是,类别分别改为checking_account和savings_account。不过 account1和account2在变量声明部分仍然使用的是类account。对于上面的例子除了对象指针变量,对于使用它的程序,它与前面的例子, 程序感觉不到他有什么区别,然而不同类型的对象会自动调用属于自己的方法。这样的好处是显而易见的,在面向过程的功能模块中为了实现不同类型帐户之间的转 账你为此不得不付出成级数倍的增长,而在面向对象的模式中我们无需改变transfer方法。
原因4:INTERFACE
INTERFACE通俗的讲就是通过一个程序来调用另外一个程序的数据和功能。在模块化和相互访问的世界中,INTERFACE提供了两个部件之间联系的 桥梁。在ABAP OO中提供了类与调用者之间独立的INTERFACE,这样就把类和使用者之间分离开来。而在ABAP OP
(oriented procedure)中没有独立的INTERFACE,而是提供了程序之间隐式的INTERFACE和程序和过程之间显式的INTERFACE。我们来进一步研究一下这两种模式的INTERFACE。
面向过程的INTERFACE
面向过程的INTERFACE有一定的局限性。
l 用TABLES或COMMON PART声明的全局变量便是程序之间隐式的INTERFACE,在经典的ABAP语言中一旦用TABLES定义了全局变量,就隐含的定义了屏幕与程序之间的接口。逻辑数据库也使用了这种隐式的接口方式。
l 一个程序的过程可以被另外一个程序调用,是程序之间的功能INTERFACE。程序中的功能模块和form都是公开的。从外边调用的其他程序的form或功能模块的程序可以访问他们所在的主程序的变量。
当用SUBMIT调用程序的时候,除了可以用selection screen上的parameters或select options作为显式的INTERFACE外,程序之间在没有其他显式的INTERFACE了。如果开发人员修改了一个程序的隐式INTERFACE, 可能会导致调用这个程序的其他程序失效而开发者确感觉不到这一点。让外部程序无限制的去访问主程序中的数据是有分险的。
OO的INTERFACE
在OO模式中可以定义INTERFACE的三个访问级别:PUBLIC,PRIVATE,PROTECTED。PUBIC表示功能或数据是对外开放的。 PRIVATE指明INTERFACE只能在类内部使用而PROTECTED则说明之有其本身或者其子类可以访问。而对于友好类则可以访问 PROTECTED和PRIVATE部分。对于子类,其继承了父类的INTERFACE,并可以对其进行扩展。
为了提高稳定性和可重用性,可以把类的PUBLIC部分单独分离出来做为类对外INTERFACE的独立描述。这种INTERFACE没有任何具体的代 码,它只有类的全部或部分全局属性。独立的INTERFACE可以被多个类使用,它代表了类的某个具体方面,比如统一持久属性等。因为他们与具体的功能无 关,所以一个对象的不同特性可以建立不同的INTERFACE,他们之间的关系如下图所示:
![11](http://scnblogs.techweb.com.cn/abaplv/files/2009/08/11-300x146.gif)
那么,实现这种机制的原理是什么呢?基本原理是你可以声明指针变量,而这个指针变量可以指向实施这个interface的任何类的对象。调用者只是与 interface指针变量工作,而其无需知道其实际指向的是哪个类的对象。而调用者只需知道这个interface的相关属性就可以了。因为实现 interface的类,就会实现在interface中定义的部分,所以只要通过interface调用者就可以实现自己的目的了。每个 interface按照自己的目的定义自己的interface方法。比如不同类型的银行帐号,利息计算的方法不同。在interface中方法名和其参 数是相同的。所以interface和继承是OO中实现多态的两种方式。通过interface指针变量来访问对象与通过父类的指针变量来访问子类的对象 的道理一样。
下面的例子演示了两个毫无关系的类,account和customer:
CLASS account DEFINITION.
PUBLIC SECTION.
INTERFACES if_serializable_object.
….
ENDCLASS.
CLASS customer DEFINITION.
INTERFACES if_serializable_object.
ENDCLASS.
DATA:account TYPE REF TO account,
Customer TYPE REF TO customer,
Serializable_objects TYPE TABLE OF REF TO if_seriazable_object,
Serializable_object TYPE REF TO if_serializable_object,
Xmlstr TYPE string.
CREATE OBJECT accout EXPORTING id = ……,
Customer.
APPEND: account TO serializable_objects,
Customer TO serializable_objects.
LOOP AT serializable_objects INTO serializable_object.
CALL TRASFORMATION id
SOURCE obj = serializable_object
RESULT xml = xmlstr.
ENDLOOP.
Account是我们所熟悉的类,customer可能是整个业务应用的一部分或其他的什么,这都无所谓。但是这些类都有统一的interface,这是 在6.10之后所支持的。本例中的interface可以使你把对象的属性序列化到xml文档中。具有INTERFACE if_serializable_object的类都具备了序列化的属性并对外提供了这种属性。
上面的例子演示了如何使用类account和customer的接口,在这个例子中,内表serializable_objects作为 if_serializable_object指针变量的集会。假如说在程序中的某个部分,内表被遍历,那么通过语句CALL TRANSFORMATION可以将其序列化。在遍历的循环中对象的类是无关的,只有序列化的对象的属性是相关的。独立的interface使你只关注对 象的某个方面,而不关注其他方面。相比之下,对于功能模块,你要输入的参数,与你手头的想要做的事情无关。
原因五,事件
应用中,用户界面的鼠标单击,新帐户的建立,都可以触发一个事件,这个事件要被他感兴趣的处理者知道。事件和事件处理与调用过程有相似之处:触发事件就相 当于调用自程序,处理事件就相当于被调用子程序的主程序。区别是被调用的子程序和调用程序是帮定的一块儿的,而事件则不同,处理者有可能处理触发的事件, 但也不一定。面向过程的ABAP的事件处理是通过运行时来实现的。而在OO中,类和对象既可以触发事件,又可以处理事件。下面将详细讨论这两种模式对事件 处理的支持。
应用中,用户界面的鼠标单击,新帐户的建立,都可以触发一个事件,这个事件要被他感兴趣的处理者知道。事件和事件处理与调用过程有相似之处:触发事件就相 当于调用自程序,处理事件就相当于被调用子程序的主程序。区别是被调用的子程序和调用程序是帮定的一块儿的,而事件则不同,处理者有可能处理触发的事件, 但也不一定。面向过程的ABAP的事件处理是通过运行时来实现的。而在OO中,类和对象既可以触发事件,又可以处理事件。下面将详细讨论这两种模式对事件 处理的支持。
面向过程的事件处理
对于面向过程的ABAPER来说事件的概念可以说是再熟悉不过了。没有事件你就无法执行ABAP程序。要想通过SUBMIT来执行一个程序,那么它必须有 个start-of-selection的事件,它是由运行时触发的。对于屏幕处理逻辑,必须能够处理PBO和PAI事件。另外在SELECTION SCREEN中你还可以处理AT SELECTION-SCREEN事件,在LIST中处理AT-LINE-SELECTION事件和AT USER-COMMAND事件。
面向过程事件的一个问题便是他的隐含性。除了两个例外外,你无法在程序里触发事件,这两个例外是在逻辑数据库的程序里可以通过PUT触发GET事件。通过 SET USER-COMMAND触发AT USER-COMMAND事件。其次便是为了写好ABAP程序,你必须了解特殊的处理流程,比如报表程序流程,对话业务流程,selection screen流程,list流程等。最后,你无法编写和处理自己的事件。这就意味着调用者和被调用模块间是一种紧密联系,一旦你在某个地方调用了某个模 块,要么这个模块存在要么就会出现运行时错误。
OO模式下的事件处理
在ABAP OO中,事件是作为类中的一部分被显式声明的。如果一个事件在类中已经被声明,那么在这个类的方法中可以通过RAISE EVENT来触发它。通过RAISE EVENT除了告诉系统即将触发一个事件外还可以通过它来传递参数给它的处理者。这个处理者便是其他类或者该类中的一个方法。这个方法静态的定义为某个具 体事件的处理者。在程序的运行过程当中事件处理者可以随时被动态的激活或使其失效,并且一个事件可以同时拥有多个事件处理者。
ABAP OO中事件处理的好处便是调用者和被调用者之间的分离。不是调用方法,而是通过触发事件来调用这个事件所有的处理者(即静态的定义好事件的处理者然后在运 行时动态的激活它)。这与传统的面向过程模式正好相反,调用者必须明确的知道它要调用什么。发布和调用的两个状态,即事件处理者的静态定义和事件处理的动 态激活为事件编程带来了很大的灵活性。下面的例子演示了如何是我们的银行帐号能够处理事件。
CLASS checking_account DEFINITION INHERIATING FROM account.
PUBLIC SECTION.
METHODS deposit REDEFINITION.
….
EVENTS adivising_required
EXPORTING value(amount) TYPE accounts-amount.
PRIVATE SECTION.
DATA: limit TYPE accounts-amount VALUE ‘5000.00’.
ENDCLASS.
CLASS checking_account IMPLEMENTATION.
METHOD deposit.
Super->deposit( amount ).
IF me->amount > limit.
RAISE EVENT advising_required
EXPORTING amount = me->amount.
ENDIF.
ENDMETHOD.
ENDCLASS.
CLASS advisor DEFINITION.
PUBLIC SECTION.
METHODS constructor.
….
PRIVATE SECTION.
….
METHODS receive_notification.
FOR EVENT advising_required OF checking_account.
ENDCLASS.
CLASS advisor IMPLEMENTATION.
METHOD constructor.
…
IF …
SET HANDLER me-receive_notification FOR ALL INSTANCES.
ENDIF.
ENDMETHOD.
…..
METHOD receive_notification.
“do something
ENDMETHOD.
ENDCLASS.
这里子类checking_account增加了事件advising_required.这个事件在重写的deposit的方法中当amount超过 limit的时候触发。在另一个类advisor中为事件advising_required定义了处理者receive_notification。在 结构方法中,通过if块决定了那些银行帐号可以触发事件。在我们的例子了advisor对象将会响应任何一个银行账号的事件,当然你也可以在SET HANDLER语句中通过single object来替换ALL INSTANCES来限制触发事件的银行账号。这样你就可以做到通过特定的银行帐号来触发advisor的receive_notification方 法。
从SAP 4.6C开始,ABAP已经成为既可以面向过程又可以面向对象的混合式语言了。使用了ABAP OO的新特征将使你的ABAP开发大受裨益。
这些新特征包括:
u 类是对象的模版
u 对象是通过指针变量来引用的
u 继承
u 独立的interface
u 对象可以触发和处理事件
前面已经详细描述了这五个特点,当然,这是所有面向对象语言都具有的特性,不过ABAP是专门面向商务应用的语言。它比java和c++更加简单,去掉了那些复杂并且容易出错的概念。它引入了触发事件和处理事件的机制,这种机制在其他语言中只有interface才具有。
如果还是没有决定是否完全的跨向ABAP OO,ABAP语言的混合性使你仍然可以把ABAP OO和面向过程的模式混合使用。这种方式既保护了已有的面向过程的ABAP产品又可以让你充分利用ABAP OO的新特性。下面将进一步从三个方面阐述如何用ABAP OO来提升你的ABAP程序。
通过ABAP OO提升ABAP程序的另外三个理由
前面主要针对OO编程的特点和方法阐述了ABAP OO的好处。不过对于ABAP本身它还有额外的好处。下面将着重介绍如何通过这些方面来以较少的成本来提升ABAP程序,尽管你还不想完全转入ABAP OO。
原因1,ABAP OO更明确更容易被使用。
使用ABAP对象更加明确容易被使用。透过表面你将会发现他有更大的益处。看下面一个简单的报表的例子。
REPORT simple_report.
NODES spfli.
GET spfli.
WRITE: / spfli-carrid, spfli-connid …
认为它简单的原因是你并不知道后台发生了什么。代码看起来简单,但是后台的运行却并不简单。通过查看文档你就会发现运行这个与逻辑数据联系在一块儿的报表 的流程至少包括十个步骤,其中至少有一个循环和两个屏幕的处理。早期的ABAP,这种方式是想给应用开发人员提供一个自动的报表处理机制。如果你能看到效 果的话,自动化从直觉上来看是很好的,否则你就会觉得无所适从。所以,如果你在一个熟悉的领域进行开发一切都OK,一旦进入到一个不熟悉的环境,一切都变 得那么高深莫测。比如:
l 我们对全局变量接口的认识,它是屏幕和应用程序的接口,也是程序和逻辑数据库之间的接口。
l 面向过程的ABAP程序很多方面都是有运行时自动控制的,他们是被屏幕或程序流所驱动的。
相比之下ABAP对象就会变得比较简单,它不容易出错,并且是由许多简单和独立的概念组合而成的。
l 类具有属性和方法
l 对象是类的实例
l 对象是通过指针变量来访问的
l 对象有独立的interface
下面的代码演示了如何把一个面向过程的逻辑数据库程序转换为面向对象的逻辑数据库程序,他会变得更加清晰。
CLASS ldb DEFINITION.
PUBLIC SECTION.
METHODS read_spfli.
EVENTS spfli_ready EXPORTING value(values) TYPE spfli.
PRIVATE SECTION.
DATA spfli_wa TYPE spfli.
ENDCLASS.
CLASS ldb IMPLEMENTATION.
METHOD read_spfli.
SELECT * FROM spfli INTO spfli_wa.
RAISE EVENT spfli_ready EXPORTING values = spfli_wa.
ENDSELECT.
ENDMETHOD.
ENDMETHOD.
ENDCLASS.
CLASS report DEFINITION.
PUBLIC SECTION.
METHODS start.
PRIVATE SECTION.
DATA spfli_tab TYPE TABLE OF spfli.
METHODS: get_spfli FOR EVENT spfli_ready OF ldb
IMPORTING values,
Display_spfli.
ENDCLASS.
CLASS report IMPLEMENTATION.
METHOD start.
DATA: ldb TYPE REF TO ldb.
CREATE OBJECT ldb.
SET HANDLER me->ge_spfli FOR ldb.
Ldb->read_spfli().
Display_spfli().
ENDMETHOD.
METHOD get_spfli.
APPEND value TO spfli_tab.
ENDMETHOD.
METHOD display_spfli.
……
ENDMETHOD.
ENDCLASS.
这里值得注意的是与面向过程的逻辑数据库相比面向对象的有了更加明确的接口。通过PUBLIC部分你可以知道这个类都提供了那些外部服务。为了运行这个报 表,你可以把这个类与OO事务代码结合起来使用。不像通过GET隐含的处理事件,report类里有明确的处理事件方法get_spfli。并且数据显示 部分也从数据处理部分分离出来了。
比较前后这两个例子,你会发现后者只需通过程序本身就可以理解整个程序的功能了。这里不存在任何黑匣子。同时通过这个例子我们也剔出了一种误解,即程序开 发的简化并不意味着肯定就是更少的代码,有时并不是这样。当然,如果你想让一个不懂ABAP REPORT的人弄明白面向过程的ABAP程序,你不得不写大量的注释。
从这个例子我们可以明显地看出ABAP oo使得程序变得更加容易理解,易于维护更加可靠。
原因2:ABAP OO在类中有更严格的语法检查
面向过程的ABAP经历了较长时间的成长,这也带来了如下结果:
l 存在着大量的过时的语句
l 存在着很多交叉的概念
l 有一些奇怪的系统行为
l 有些技术显得较难理解
由于不能影响已有的代码,ABAP OO的版本引入了更加严格的语法检查以剔出面向过程的ABAP程序一些弊病,主要包括:
l 禁止使用过时的ABAP语句
l 使一些隐含性的语法变得更加明确
l 查明并禁止不正确的数据处理
注意:
向下兼容的一些ABAP语法在ABAP OO之外仍然被支持,这样你就没必要为了使用的新版本的ABAP而去修改原来的程序。从另一个角度来讲,由于有大量的现存程序存在,不得不保留用新的概念 来剔出一些过时的概念对语言本身来讲也是一个沉重的负担。如果你在ABAP对象之外使用下面列表中以外的功能,语法检查最多也只能给你个警告的信息。如果 想去掉这些警告信息,最好使用下面列表中的替代方式。
Context Description
Syntax notation No special characters in names; no negative length specifications, no multi-line literals
Declarations LIKE references to data objects only; no implicit length of specifications for data types I, f, d or t; no operational statements in structure definitions;FIELDS,RANGES,INFOTYPES,TABES,NODES,COMMON PART,OCCURS,NO-LOCAL not permitted
Operations CLEAR…WITH NULL,PACK,MOVE…PERCENTAGE;ADD-CORRESPONDING;DIVIDE-CORRESPONDING;SUBSTRACT-CORRESPONDING,MULTIPLY-CORRESPONDING;ADD THEN…UNTIL…,ADD FROM…TO…;CONVERT(DATE INVERTED DATE) not permitted
String Processing Not permitted on numeric processing
Field Symbols No implicit types; FIELD-SYMBOLS…STRUCTURE;ASSIGN…TYPE;ASSIGN LOCAL COPY OF,ASSIGN TABLE FIELD not permitted.
Logic expressions Operators ><,=<;= not permitted; table used with IN must be a selection table
Control structures No operational statements between CASE and WHEN;ON-ENDON not permitted
Internal Tables No header lines; no implicit work areas; no redundant key specifications; compatible work areas required where necessary; obsolete READ variants, COLLECT,SORTED BY,WRITE TO itab , PROVIDE not permitted
Procedures(methods) No implicit type assignment; compatible initial values only; passing of sy-subrc and raising of undefined exceptions not permitted
Program calls No joint of USING and SKIP FIRST SCREEN when calling transactions;passing formal parameters implicitly in CALL DIALOG not permitted
Database accesses No implicit work areas in Open SQL;READ,LOOP,REFRESH FROM on database tables not permitted; VERSION addition to DELETE and MODIFY not permitted; no PERFORMING addition in Native SQL
Data clusters No implicit identifiers; no implicit parameter names; no implicit work areas; MAJOR-ID and MINOR-ID not permitted
Lists DETAIL,SUMMARY,INPUT,MAXIMUM,MINIMUM,SUMMING,MARK,NEW-SECTION, and obsolete parameters are not permitted
上面的表格列举了在类中一些语法的限制,使用上面列表中的任何一个限制的语法都会斗志语法检查的错误。可以在ABAP文档中找到这些限制的替代方式。
下面通过举例说明使用更加清晰的ABAP OO语法的好处:
在传统的面向过程的语法中定义内表的语句如下:
DATA BEGIN OF itab OCCURS 0.
…
CLEAR itab.
而在ABAP OO中就只能采用下面这种方式定义内表了:
DATA itab TYPE TABLE OF …
…
CLEAR itab.
过去的内表的定义方式非常容易引起混淆。这种方式除了定义了内表变量,它也同时定义了一个work area。一个名字代表两个变量显然非常容易引起混淆。在ABAP OO中对此进行了严格的约束,内表的定义没有隐含的定义表头,那么CLEAR语句就是明确的清除内表的内容了。而且在传统的ABAP程序中,你不得不去判 断你所操作的是内表还是表头,这样便很容易导致程序出错。而在ABAP OO中禁止了这种表头,那么就避免容易产生此种错误的弊病,它要求必须声明一个独立的表头变量来处理内表中的内容从而使程序变得更加清晰而易于维护。
下面的例子比较了两种模式下从数据库中取数的不同之处:
在面向过程的ABAP程序中:
TABLES:dbtab.
SELECT * FROM dbtab.
在面向对象的ABAP程序中
DATA wa TYPE dbtab.
SELECT * INTO wa FROM dbtab.
TABLES隐含的声明了一个与数据库表名相同的结构变量,那么对数据库表内容的操作都使用这个隐含的变量。而在ABAP OO中,这种隐含的方式是不被允许的。TABLES语句是禁用的,必须使用更加明确的语句。
下面是另一个例子:
在面向过程的ABAP程序中:
FIELD-SYMBOLS <fs>.
IF <fs> IS ASSIGNED.
ENDIF.
在面向对象的ABAP程序中:
FIELD-SYMBOLS <fs> TYPE ANY.
IF <fs> IS ASSIGNED.
ENDIF.
在ABAP OO中形参和FIELD SYMBOL必须明确的指定类型,而在过程的ABAP程序中是没有必要的。如果没有明确指定FIELD SYMBOL的类型,ABAP运行时就会自动把SPACE分配给这个field symbol。这样的话在过程的ABAP程序中判断语句的返回结果为TRUE,而在ABAP OO中则为false。
最后一个例子如下:
在过程的ABAP程序中;
DATA number TYPE I VALUE ….
TRANSLATE number TO UPPER CASE.
在ABAP OO中
DATA number TYPE I VALUE …
TRANSLATE number TO UPPER CASE.
在过程的ABAP程序中对数字型的字符型操作abap运行时会给警告信息,而在ABAP OO中则会给错误信息。在ABAP OO中,系统运行时保证了对应的操作必须针对对应的变量。
原因3:ABAP OO支持一些新的技术
SAP许多新的技术都是基于ABAP OO的,那么在你的项目中必须对面对这一点。许多基础的技术都在ABAP OO中发布了,这些包括:
l User dialog的架构,如SAP control framework(CFW),Business Server Pages(BSP)以及Desktop Office Integration(DOI)等。
l Frameworks for persisting data in the database(Object Service) and Shared Objects(area classes)
l Service classes CL_GUI_FRONTEND_SERVICES for working with data at the presentation server
l Laguate related classes,such as Run Time Type Services(RTTS) classes on CL_ABAP_EXPIMP subclasses for extended EXPORT/IMPORT functionality for example.
原因3:ABAP OO支持一些新的技术
SAP许多新的技术都是基于ABAP OO的,那么在你的项目中必须对面对这一点。许多基础的技术都在ABAP OO中发布了,这些包括:
l User dialog的架构,如SAP control framework(CFW),Business Server Pages(BSP)以及Desktop Office Integration(DOI)等。
l Frameworks for persisting data in the database(Object Service) and Shared Objects(area classes)
l Service classes CL_GUI_FRONTEND_SERVICES for working with data at the presentation server
l Laguage related classes,such as Run Time Type Services(RTTS) classes on CL_ABAP_EXPIMP subclasses for extended EXPORT/IMPORT functionality for example.
SAP的趋势是一些新的基础服务都将用面相对象的模式开发,这就意味着如果想应用一些新的技术那你就不得不接触ABAP OO。如果在一些新的项目中不得不使用ABAP OO的架构强烈建议使用OO的方式进行开发。尽管面向对象和面向过程的混合模式在ABAP程序中是允许存在的,但是不建议这种情况的出现,因为这样反而使 成变得更加不易于维护。应尽量避免使用面向过程的一些概念来开发程序,除了下面一些特殊的情况:
l 把屏幕封装在模块池中,一些需要远程调用的功能模块。
l 内部模块化应尽量避免使用subroutine,他们的参数没有方法先进。Subroutine的参数与位置有关而方法是与参数名结合到一块的。另外 subroutine支持指针变量。另外subroutine参数的类型的检查只在运行时有效,而method在编译时就检查。建议使用类方法 CLASS-METHODS而不是subroutine。
结论
使用的面向对象的方法便是使用现代的软件的开发技术,它解决了一些复杂开发项目的问题。通过本文我们了解到了面向对象的方法有如下好处:
l Better encapsulation
l Support for multiple instantiation
l Better technique for reusing code
l Better interfaces
l An explicit event concept
即便你还不想完全转入ABAP OO,也见你多利用ABAP OO的特性来增强程序的质量
l 尽量使用method
l 使用static method来代替subroutine
l 只有在必要的时候才使用function module
l 尽量把面向过程和面向对象的源码分离开来
l 把屏幕逻辑的开发和应用逻辑的开发分离开来
l 不需要去掉‘UNICODE CHECK ACTIVE’
通过这篇文章的论述,对你程序开发的方式提供了新的思路。我们希望能够说服你转向ABAP OO。当然这种转换并不是让你接触更加复杂的语言,而是为你提供了更好的机会和灵活性来满足你的要求:
l 从最低层次的应用来讲,如果你在开发报表,你可能不需要来实例化类,而使用类的static method来实现报表逻辑,你仍然可以看到你的程序被改进了
l 从较高层次的应用来讲,你可能在一个项目中应用OO的模式。这样的项目涉及到OO开发的很多细节阶段,这也超出了本文的范围。
对于大多开发者来说,情况总是介于两者之间。想其他面向对象的语言一样,面向对象的开发并意味着你都要在UML方面付出太多的精力。不必担心ABAP OO的应用会给你带来更多的麻烦。一旦你真正使用了它,你就会觉得我为什么不早用它呢!
将经典的屏幕逻辑从OO的应用程序中分离出来
正如你所看到的,在大部分程序中屏幕逻辑和应用逻辑是紧密地联系在一起的。这些程序都是用户通过在屏幕中输入数据来驱动的,应用逻辑都写在modue或 event block中。而对于ABAP OO来说,它主要是用来写应用逻辑而非屏幕逻辑。面向对象的ABAP OO不支持屏幕编程。
如果你想用OO的模式编写ABAP程序,你可能面临的问题便是如何把屏幕程序与之结合起来。
答案便是把应用逻辑从程序界面中分离出来。下面的一个简单的例子讲给你演示如何做到这一点。
下面的代码演示了如何通过模块池把应用程序需要的屏幕封装在一起。
FUNCTION-POOL flight_screens.
SELECTION-SCREEN BEGIN OF SCREEN 100 AS WINDOW.
PARAMETERS: p_carrid TYPE sflight-carrid,
P_connid TYPE sflight-connid,
P_fldate TYPE sflight-fldate.
SELECTION-SCREEN END OF SCREEN 100.
TABLES sflight.
FUNCTION get_flight_parameters.
CALL SELECTION-SCREEN 100 STARTING AT 10 10.
Carrid = p_carrid.
Connid = p_connid.
Fldate = p_fldate.
ENDFUNCTION.
FUNCTION get_plane_type.
Sflight-planetype = plane_type.
CALL SCREEN 200 STARTING AT 10 10.
Plane_type = sflight-planetype.
ENDFUNCTION.
在这个例子中包括一个selection screen100和一个screen 200。模块池和一般屏幕之间的接口通过TABLES语句来声明。功能模块来处理屏幕并通过参数来输入输出数据。
如果模块池中需要内部模块,可以通过本地类来实现。如果模块池屏幕中包含GUI控件,相应的事件处理也应当在模块池的本地类中实现。在这个简单的例子中并没有screen module。
如果你想使用输入检查这样的自动机制,那么你就要使用这些screen module了。不过要记住在这些module中要输入尽量少的代码而要尽量去调用本地类的方法。另外考虑到数据的封装性,应尽量减少直接操作全局变量。
下面的一段代码演示了如何在类flights中使用功能模块get_plane_type的屏幕。
CLASS flights DEFINITION.
PUBLIC SECTION.
METHOS: constructor,
Change_plane_type.
PRIVATE SECTION.
DATA flight TYPE sflight.
ENDCLASS.
CLASS flights IMPLEMENTATION.
METHOD constructor.
DATA: carrid TYPE sflight-carrid,
Connid TYPE sflight-connid,
Fldate TYPE sflight-fldate.
CALL FUNCTION ‘GET_FLIGHT_PARAMETERS’
IMPORTING
Carrid = carrid
Connid = connid
Fldate = fldate.
SELECT SINGLE * FROM sflight
INTO flight
WHERE carrid = carrid AND
Connid = connid AND
Fldate = fldate.
ENDMETHOD.
METHOD change_plane_type.
CALL FUNCTION ‘GET_PLANE_TYPE’
CHANGING plane_type = flight-planetype.
ENDMETHOD.
ENDCLASS.
这个类中的每个对象代表着表sflight中的一个flight。创建对象时,类的结构方法便会调用功能模块来取得选择屏幕上的业务主键。方法change_plane_type来处理屏幕200要求输入plane type,但是在类中没有任何屏幕逻辑。
只在应用程序中使用ABAP OO并将其与屏幕逻辑分离开来将为未来表现层技术的变革打下基础(比如SAP GUI和BSP)。屏幕逻辑和应用逻辑不再混合在一起,新的表现层的技术变革将限制一些过程的调用。不过有趣的是这种分离在面向过程的程序开发中也可以是 实现只不过并没有强制。简单例证便是屏幕并不允许被作为类的组件。所以现在就应当尽量熟悉这种方式。