ABAP OO的八大理由(-)
原贴地址:http://scnblogs.techweb.com.cn/abaplv/archives/127.html
几年前SAP BASIS 4.6为ABAP扩展了OO功能,这是很多传统的ABAP程序员陷入困境。首先对于ABAP程序员来说它们终于可以通过OO这种开发方法得潜在价值来提高 开发效率和所开发程序的可维护性了。然而一直以来习惯于面向过程编程的ABAPER迅速的转向OO模式也不是那么容易,很多人还是坚持使用传统的面向过程 的开发模式,他们觉得不用OO也可以得到他们想要的。这篇文章的主要目的便是让你明白OO能给你带来什么以及如何转型为OO模式。
1. ABAP OO更加明确所以更易于使用。例如在使用ABAP OO你的程序的执行流程不再是由运行时隐含的控制。这样你就可以自己去设计程序所执行的流程了而不必像面向过程那样去了解和服从外部控制机制(即报表和 dialog screen的事件)。
对于面向过程的模式,程序的运行通常是从 screen 的 dialog module 或 selection screen 的 start-of-selection 事件开始的。你在这些处理模块中操作全局变量来实现需求的功能。你可以通过内部的 form 和外部的 function module 来实现程序的模块化。这些过程除了可以操作全局变量外还可以具备内部的本地变量来协助实现内部的一些特定功能。 对于OO编程,唯一的结构单位就是类,这里类的实例对象取代了全局变量。这些对象封装了应用的状态和行为。应用的状态是用属性来代表的它取代了面向过程中的全局变量。应用的行为是通过方法来实现的,他们用来改变应用的属性或者调用其它对象的方法。 n 目前大部分程序都是面向过程和ABAP OO 的混合体如下图所示:
![]() 虽然纯粹的OO模式技术上是可行的,但是现实中还存在着大量的两种模式的混合体如右面的图所示。ABAP 对象和面向过程的技术同时应用,调用常用的功能模块,调用屏幕或者直接访问数据库等在对象中都存在。混合的模式及利用了新技术又保护了已投入的成本。 两种模式的选择 正如本文所述,OO的模式是最佳的选择,除非在绝对必要的情况下才使用面向过程的模式。比如传统的screen programming在OO中是不支持的,附录中会进一步阐释如何实现screen与OO的结合。 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中通过继承来实现代码的可重用性,可以通过通用的父类来派生特殊的子类。这样好处是简单的类既可代替代码庞杂的模块池,简单的方法参数既可代替功能模 块复杂的参数,最重要的是通过继承实现了类的多态。这样就实现了子类的对象仍然可以使用父类的方法。这大大增强了软件开发的建模能力。 下面的例子是一个修改版的帐户,他已经作为两个子类checking account和saving account的父类。
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….ENDINTERFACE来定义。它可以有与类一样的部件。任何类都可以有一个或多个 INTERFACE。通过在类的public部分进行声明,这样在interface中定义的public部分就成为类public interface的一部分。包含独立interface的类必须为类提供服务,这样用户可以通过interface来访问类,而无需知道其中的访问细 节。
那么,实现这种机制的原理是什么呢?基本原理是你可以声明指针变量,而这个指针变量可以指向实施这个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方 法。 是否已经决定转向ABAP OO了
从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 学会ABAP OO一些简单的语句之后,你就可以通过开发程序直接告诉系统它需要做什么。需要系统做什么是由你明确告诉你的,系统不会做任何隐含的动作。你不需要为了了解系统一些隐含的动作而去读大量的文档,你只需去看程序本身就可以了。
下面的代码演示了如何把一个面向过程的逻辑数据库程序转换为面向对象的逻辑数据库程序,他会变得更加清晰。 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文档中找到这些限制的替代方式。
下面的例子比较了两种模式下从数据库中取数的不同之处:
原因3:ABAP OO支持一些新的技术
将经典的屏幕逻辑从OO的应用程序中分离出来 |
|