5.1 面向对象设计的基本概念
5.2 封装
5.2.1 封装的概述(Encapsulation)
封装过程就好比你是一名电器设计师,需要考虑如何设计并制作一台空调,你不仅需要了解空调的原理,并且还要设计出简单易用的方式,提供给普通用户。
用户不必知道里面的电路和压缩机是如何工作的,只要用户会用遥控器,就能让空调正常工作。
如果我们不为空调提供遥控器,而是直接把空调的电线和电路都暴露在外壳外面,使用时需要用户直接对裸露的电线手工对接通电,然后再调整压缩机来设定温度,这不仅不是简单的操作,实际上还是十分危险的操作。
类的封装与上述关于空调的使用比较类似,类封装了一些数据和方法(比如空调的三个方法:开启空调,关闭空调,调节温度);类的方法的具体实现也很复杂(比如空调这些方法的实现都涉及了电路操作、压缩机调节等对于普通用户来说有相当难度的细节)。而我们没有必要让用户了解如此复杂的细节,用户只需要正确地按照说明书使用,即可让空调正常工作。
就像C++的设计者Stroustrup所说的:”隐藏是为了防止事故,而不是防止欺骗(Hiding is for the prevention of accidents,not the prevention offraud)。
对于封装这一术语,我们还是查一下字典,体会一下其原意。我们用《牛津词典》查看原词(Encapsulation)的解释:
Encapsulation~sth(in sth)(formal)to express the most importantparts of sth in a few words,a small space or a single object——简述;概括;压缩。
5.2.2 封装的实现
,一般来说,数据属性是不能直接访问的,而是要通过相关的存取方法按规则进行存取。
类的数据属性通常定义为private,而方法声明为public,方法可供外部调用,但除了方法名称和参数,方法内部的详细实现对外部是不可见的,这样一来,对类的字段的读写就可以由类的方法来控制读写的可靠性了。同时也向外部提供了明确而且唯一的调用方法,从而简化和统一了调用。
业务需求:本例是创建一个银行账户的示例类,用于展示类封装的概念,类中包含一个账户号码、一个账户当前金额,这些信息是不可以直接存取的。余额和账户号码的操作需要做相应的验证才可以进行存款和取款的操作,所以类中还有几个方法,用于对属性进行存取,另外还有存入和取出操作的方法。
·账户金额是属性,为私有,只能通过公有的方法对账户余额进行存取。
·账户号码是属性,为公有,外部程序可以任意进行存取(从中对比一下公有和私有的区别)。
创建全局类
创建属性
创建方法
get_account_number参数
get_account_number 方法的代码
如图5-9所示,设定当前账户号码时做一个内部的逻辑验证,确定账户含有CA277字符,才认为其是合理的账户号码,否则设定为0000000000,这样外部调用程序就不用关注该类方法内部的详细验证逻辑了。
图5-9 设定内部的逻辑验证(SE24)
类代码:
class ZCL_ACCOUNT_BALANCE definition
public
final
create public .
public section.
data MV_ACCOUNT_NUMBER type STRING10 .
methods GET_ACCOUNT_NUMBER
returning
value(RV_ACCOUNT_NUMBER) type STRING10 .
methods SET_ACCOUNT_NUMBER
importing
!IV_ACCOUNT_NUMBER type STRING10 .
methods GET_ACCOUNT_BALANCE
returning
value(RV_ACCOUNT_BALANCE) type DMBTR .
methods SET_ACCOUNT_BALANCE
importing
!IV_ACCOUNT_BALANCE type DMBTR .
methods DEPOSIT
importing
!IV_DEPOSIT_AMOUNT type DMBTR
returning
value(RV_ACCOUNT_BALANCE) type DMBTR .
methods WITHDRAW
importing
!IV_WITHDRAW_AMOUNT type DMBTR
returning
value(RV_ACCOUNT_BALANCE) type DMBTR .
protected section.
private section.
data MV_ACCOUNT_BALANCE type DMBTR .
ENDCLASS.
CLASS ZCL_ACCOUNT_BALANCE IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->DEPOSIT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_DEPOSIT_AMOUNT TYPE DMBTR
* | [<-()] RV_ACCOUNT_BALANCE TYPE DMBTR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD deposit.
IF mv_account_number NE '0000000000' AND iv_deposit_amount > 0.
mv_account_balance = mv_account_balance + iv_deposit_amount.
rv_account_balance = mv_account_balance.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->GET_ACCOUNT_BALANCE
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_ACCOUNT_BALANCE TYPE DMBTR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method GET_ACCOUNT_BALANCE.
rv_account_balance = mv_account_balance.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->GET_ACCOUNT_NUMBER
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_ACCOUNT_NUMBER TYPE STRING10
* +--------------------------------------------------------------------------------------</SIGNATURE>
method GET_ACCOUNT_NUMBER.
rv_account_number = mv_account_number.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->SET_ACCOUNT_BALANCE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_ACCOUNT_BALANCE TYPE DMBTR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SET_ACCOUNT_BALANCE.
mv_account_balance = iv_account_balance.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->SET_ACCOUNT_NUMBER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_ACCOUNT_NUMBER TYPE STRING10
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD set_account_number.
DATA: lv_str TYPE string VALUE '0000000000'.
lv_str = iv_account_number.
SHIFT lv_str LEFT DELETING LEADING '0'.
IF lv_str CP 'CA277*'.
mv_account_number = iv_account_number.
ELSE.
mv_account_number = '0000000000'.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ACCOUNT_BALANCE->WITHDRAW
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_WITHDRAW_AMOUNT TYPE DMBTR
* | [<-()] RV_ACCOUNT_BALANCE TYPE DMBTR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method WITHDRAW.
IF mv_account_balance >= iv_withdraw_amount.
mv_account_balance = mv_account_balance - iv_withdraw_amount.
rv_account_balance = mv_account_balance.
ENDIF.
endmethod.
ENDCLASS.
调用类的报表程序。
REPORT Z000501.
DATA: go_account_balance TYPE REF TO zcl_account_balance.
CREATE OBJECT: go_account_balance.
PARAMETERS:
p_numbr TYPE string10,
p_blnce TYPE dmbtr,
p_dpst TYPE dmbtr,
p_wdrw TYPE dmbtr.
DATA:gv_balance TYPE dmbtr.
* Public attribute could be access directly.
*go_account_balance->mv_account_number = '12111111232'.
*OOP support this format for calling method
CALL METHOD go_account_balance->set_account_number
EXPORTING iv_account_number = p_numbr.
p_numbr = go_account_balance->get_account_number( ).
WRITE:/ 'Account ', p_numbr ,' Account Balance: ', p_blnce.
CALL METHOD go_account_balance->set_account_balance( p_blnce ).
WRITE:/ 'Set Account balance to ', p_blnce.
*OOP support this format for calling method -
CALL METHOD go_account_balance->deposit
EXPORTING iv_deposit_amount = p_dpst
RECEIVING rv_account_balance = gv_balance.
*New format of calling method.
gv_balance = go_account_balance->get_account_balance( ).
WRITE:/ 'Deposited ', p_dpst ,'RMB, now making balance to ',gv_balance.
gv_balance = go_account_balance->withdraw( p_wdrw ).
gv_balance = go_account_balance->get_account_balance( ).
WRITE:/ 'Withdrew ', p_wdrw ,'RMB, now making balance to ',gv_balance.
输入:
结果:
封装的大致原则具体如下。
1)在分析和设计阶段,需要采用良好的设计,符合面向对象的设计原则,使得封装的结果能够为当前业务服务,并且能够接纳未来的业务变化,提高重用性,免得需求发生变化或代码升级时导致软件结构“伤筋动骨”。
2)软件实现阶段,需要定义并实现完整且功能单一、简洁的方法,便于外部调用。
3)软件实现阶段,把尽可能多的数据隐藏起来,对外提供简洁的存取接口,也就是隐藏数据,暴露接口。
5.3 继承 Inheritance
5.3.1 继承的概述(Inheritance)
我们经常听说面向对象包含三个要素,分别是“封装”“继承”和“多态”,其实在面向对象的设计中,第二个要素“继承”和第三个要素“多态”是一体的,可以说多态是继承的目的,继承是多态的前提。
被继承的类称为“基类”“父类”或“超类”(Parent Class or SuperClass),通过继承创建的新类称为“子类”或“派生类”(Sub Class)。
面向对象的设计中,当B的每个实例都可以被看作A中的一个时(也就是IS-A关系时),这样的关系才适合定义为B继承A,也就是说如果用自然语言能够表达为“每一个(一类/一种)B都是A”,或者“B是A中的一个(一类/一种)”,那么可定义为B类继承A类。
面向对象的继承和生物学上的概念也不太相似。相比之下,面向对象的继承概念比较靠近社会生活中对权力和责任的继承。
面向对象的语言中,包括Java、C#、PHP、ABAP OOP等都是单继承,也就是一个类只能从一个类中继承(单继承),不允许从多个类继承(多继承),但一个类可以被多个子类继承。
一些OOP可以实现多继承,如C++和Python,允许从多个类(≥2个类)中继承,子类可获得各个类中的相应属性(也就是从≥2的父类中继承),但这些继承在缺乏经验的设计下往往会导致更多的问题。
5.3.2 继承的实现
1.业务实例
我们还是使用物料为例进行说明,定义一个“物料”父类,然后派生一个“原料”子类,让“原料”子类继承“物料”父类的一些属性和方法,再定义“原料”子类自己所特有的一些属性和方法。
在继承中,子类和父类具有以下规则。
·子类拥有父类非Private的(即Public和Protected)属性和方法
·子类可以添加自己的属性和方法,即子类可以对父类进行扩展。
·子类可以用自己的方式重新定义继承所得的父类的方法,即方法的覆盖。
本例中,我们会创建父类ZCL_SUPER_CLASS_MATL,然后创建子类ZCL_SUB_CLASS_MATL_RAW,使子类继承自父类ZCL_SUPER_CLASS_MATL。
增加类(父类)属性,方法。
点击基于源代码。 编辑方法的代码。 激活。
class ZCL_SUPER_CLASS_MATL definition
public
create public .
public section.
data MV_MATERIAL_ID type MARA-MATNR .
methods SET_MATERIAL_ID
importing
!IV_MATERIAL_ID type MARA-MATNR .
protected section.
data MV_MATERIAL_NAME type MAKT-MAKTX .
methods GET_MATERIAL_NAME
returning
value(RV_MATERIAL_NAME) type MAKT-MAKTX .
private section.
data MV_MATERIAL_DESC type STRING .
methods GET_MATERIAL_DESC
returning
value(RV_MATERIAL_DESC) type STRING .
ENDCLASS.
CLASS ZCL_SUPER_CLASS_MATL IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_SUPER_CLASS_MATL->GET_MATERIAL_DESC
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_MATERIAL_DESC TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
method GET_MATERIAL_DESC.
rv_material_desc = mv_material_desc.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_SUPER_CLASS_MATL->GET_MATERIAL_NAME
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_MATERIAL_NAME TYPE MAKT-MAKTX
* +--------------------------------------------------------------------------------------</SIGNATURE>
method GET_MATERIAL_NAME.
rv_material_name = mv_material_name.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUPER_CLASS_MATL->SET_MATERIAL_ID
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_MATERIAL_ID TYPE MARA-MATNR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SET_MATERIAL_ID.
mv_material_id = iv_material_id.
WRITE: / 'Material:', mv_material_id.
endmethod.
ENDCLASS.
建子类ZCL_SUB_CLASS_MATL_RAW,继承自父类ZCL_SUPER_CLASS_MATL。
父类中public和protected的属性及方法自动带出来。
子类中重写父类的方法:
参数:
代码
METHOD get_material_type.
DATA: ls_mara TYPE mara.
SELECT SINGLE mtart FROM mara
INTO (ls_mara-mtart)
WHERE matnr = iv_material_id.
IF sy-subrc = 0.
rv_material_type = ls_mara-mtart.
else.
rv_material_type = ''.
ENDIF.
ENDMETHOD.
类的源代码:
class ZCL_SUB_CLASS_MATL_RAW definition
public
inheriting from ZCL_SUPER_CLASS_MATL
final
create public .
public section.
methods GET_MATERIAL_TYPE
importing
!IV_MATERIAL_ID type MARA-MATNR
returning
value(RV_MATERIAL_TYPE) type MARA-MTART .
methods SET_MATERIAL_ID
redefinition .
protected section.
private section.
data MV_MATERIAL_TYPE type MARA-MTART .
ENDCLASS.
CLASS ZCL_SUB_CLASS_MATL_RAW IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUB_CLASS_MATL_RAW->GET_MATERIAL_TYPE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_MATERIAL_ID TYPE MARA-MATNR
* | [<-()] RV_MATERIAL_TYPE TYPE MARA-MTART
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_material_type.
DATA: ls_mara TYPE mara.
SELECT SINGLE mtart FROM mara
INTO (ls_mara-mtart)
WHERE matnr = iv_material_id.
IF sy-subrc = 0.
rv_material_type = ls_mara-mtart.
else.
rv_material_type = ''.
ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUB_CLASS_MATL_RAW->SET_MATERIAL_ID
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_MATERIAL_ID TYPE MARA-MATNR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SET_MATERIAL_ID.
*CALL METHOD SUPER->SET_MATERIAL_ID
* EXPORTING
* IV_MATERIAL_ID =
* .
mv_material_id = iv_material_id.
write :/ 'Raw Material: ', mv_material_id.
endmethod.
ENDCLASS.
2.子类的方法重定义
一般称为重写或覆盖(override)
重定义方法时有如下一些注意事项。
1)如果在父类中设定一个方法为Final状态(是方法的状态而不是类的状态为Final),则该方法不可以在子类中重新定义。
2)重新定义方法时,方法的参数是不能改变的
3)对于继承的父类的构造器,是不可以重新定义的
4)对于父类的非抽象方法,在子类中重新定义方法违反了面向对象的一个重要的设计原则——里氏替换原则(LSP)。违反LSP的结果就是,程序的面向对象设计不完美,对于复杂的面向对象程序,有很大的概率会导致逻辑错误。“多态”中会介绍其他方法,既可以重新定义子类方法,还可以满足LSP。
5)子类如果重定义父类方法METHOD01,则子类可以在本方法内通过“CALL MET-HOD SUPER->METHOD01.”,来对父类的代码进行调用,并且可以取得父类方法的返回值,继续自己的业务处理。但“CALL METHODSUPER->METHOD01”。只能在子类方法METHOD01中调用,不可以放在其他方法内如METHOD02中调用。如果调用,则会出现“You can only use SUPER->to call previous implementations of the class ownmethods(SUPER->)”错误信息。
3.测试父类和子类
代码:
*& Report Z000510
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT Z000510.
DATA : "declare super material class
go_super_material TYPE REF TO zcl_super_class_matl,
"declare sub material class
go_sub_material_raw TYPE REF TO zcl_sub_class_matl_raw,
gv_material_id TYPE mara-matnr.
START-OF-SELECTION.
PARAMETERS p_matnr TYPE mara-matnr. "material input
gv_material_id = p_matnr.
WRITE : /, / '1. 测试子类对象'.
" 创建子类对象
CREATE OBJECT go_sub_material_raw.
"Call Sub Class Methods 调用子类对象方法
CALL METHOD go_sub_material_raw->set_material_id( p_matnr ).
WRITE : /, / '2. 测试父类对象'.
" create instance for super class 创建父类对象
CREATE OBJECT go_super_material.
"Call Super Class Methods 调用父类对象方法
CALL METHOD go_super_material->set_material_id( p_matnr ).
表5-7 测试结果
继承的好处具体如下。
1)可以提高程序的可复用性。
2)可以实现多态。这才是继承的根本目的。
但继承的缺陷也很明显,具体如下。
1)耦合性很强:
2)继承也从一定程度上破坏了封装。
基于以上的讨论,我们对待继承的态度是——应谨慎使用继承。
那么什么时候要使用继承呢?《Java编程思想》中的建议是:查看程序中是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的;但是如果不需要,则应当好好考虑自己是否需要继承。
面向对象的继承有两种实现方法:实现继承和接口继承。
5.4 多态(Polymorphism)
5.4.1 多态的概述
《柯林斯英语词典》查看一下“多态”一词的原始意义:
·生物学:在单一的种群中出现多种形式的个体的现象,同一物种形态构造表现为多种不同的个体,如蜜蜂的不同个体,有蜂王、雄蜂和工蜂。
·化学:同素异形体,同一化合物可以形成不同类型的形态,如碳的同素异形体有金刚石和石墨等。
多态(Polymorphism)的意义是“一个实体同时具有多种形态”。多态是面向对象程序设计(OOP)的一个重要特征。在面向对象的程序中,多态指的是对不同的对象执行同一个操作,程序可以有不同的解释,执行不同的逻辑,并返回不同的执行结果。
实现多态有三个前提条件,具体如下。
1)多态发生在有继承关系的类之间(继承的主要目的也是为了多态)。
2)子类要对父类方法进行重写覆盖(重写覆盖就是功能细分的实现)。重定义(Redefine)
3)父类引用指向子类对象(向上转型就是多态的标志)。“向上转型”(upcasting)
向上转型的代码实现:
*&---------------------------------------------------------------------*
*& Report Z000514
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000514.
"父类水果,从ABAP根类object继承
CLASS
zcl_fruit DEFINITION INHERITING FROM object.
ENDCLASS.
"子类苹果,集成自水果类
CLASS
zcl_apple DEFINITION INHERITING FROM zcl_fruit.
ENDCLASS.
" 子类梨子,继承自水果类
CLASS
zcl_pear DEFINITION INHERITING FROM zcl_fruit.
ENDCLASS.
"定义对象
DATA go_fruit TYPE REF TO zcl_fruit.
DATA go_apple TYPE REF TO zcl_apple.
DATA go_pear TYPE REF TO zcl_pear.
"第一种向上转型,按照子类类型创建父类对象
"父类是可创建对象实例的类,或者是抽象类和接口
CREATE OBJECT go_fruit TYPE zcl_apple.
"或者使用 New 语句
go_fruit = new zcl_apple( ).
"第二种向上转型,如果父类是可常见对象实例的类,
"那么分别创建子类和父类对象后,采用一般赋值语句。
CREATE OBJECT go_fruit.
CREATE OBJECT go_apple.
go_fruit = go_apple.
"第三种向上转型, 如果父类是可创建对象的类,
"那么分别创建子类和父类对象后,采用 move to 语句。
CREATE OBJECT go_fruit.
CREATE OBJECT go_apple.
MOVE go_apple to go_fruit.
在上面的例子里, go_fruit的类型是 zcl_fruit。 转型后,zcl_apple的类有了一个类型为Zcl_fruit的实例 go_fruit.
“强制向下转型”(Downcasting)。
强制向下转型的主要目的是为了能够让父类对象调用子类特定的方法。
向下转型的方法与步骤具体如下。
设定父类对象为go_super,子类对象为go_sub,子类的类为zcl_sub。
1)如果子类经过向上转型到父类,则父类对象可以直接强制向下转型。强制向下转型采用“?=”进行赋值,要将父类对象赋值给子类对象,这个转换是强制的:
lo_sub ?= lo_super.
2)强制向下转型的另一种格式是“MOVE ?TO”,需要在TO之前加一个问号,以表示强制转型:
MOVE lo_super ?TO lo_sub.
3)也可采用SAP的关键字CAST进行强制向下转型,格式为“CAST子类类型(父类对象)”,如:
CAST zcl_sub( go_super ).
4)强制向下转型之前,如果父类对象没有经过子类的向上转型,则直接进行强制向下转型的话,会引发错误。
可以先向上转型,如果父类不能创建实例的抽象类或接口,则可以参照向上转型的第一种方法,创建对应的父类对象时采用子类类型创建。如:
CREATE OBJECT lo_super TYPE zcl_sub_class.
然后再进行强制向下转型。
试图用父类型创建出子类型对象的“强制向下转型”是不可行的,如:
CREATE OBJECT go_apple TYPE zcl_fruit.
会报出“The specified type cannot be converted into the target variables.”错误。
如示例程序5.15所示,强制向下转型是需要类型验证的,如“苹果”和“梨子”向上转型为“水果”后,可以强制向下转型为“苹果”或者“梨子”。在ABAP 7.5版本后,可以使用“IS INSTANCE OF”操作符来判断具体的类对象的类型,对具体类型进行验证。
向下代码实现:
&---------------------------------------------------------------------*
*& Report Z000515
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000515.
*&---------------------------------------------------------------------*
"定义素食类
CLASS
zcl_vegetarian_food DEFINITION INHERITING FROM object.
ENDCLASS.
"定义水果类,继承自素食类
CLASS
zcl_fruit DEFINITION INHERITING FROM zcl_vegetarian_food.
ENDCLASS.
"定义苹果类,继承自水果类
CLASS
zcl_apple DEFINITION INHERITING FROM zcl_fruit.
ENDCLASS.
"定义梨子类,继承自水果类
CLASS
zcl_pear DEFINITION INHERITING FROM zcl_fruit.
ENDCLASS.
"定义类变量
DATA go_vegetarian_food TYPE REF TO zcl_vegetarian_food.
DATA go_fruit TYPE REF TO zcl_fruit.
DATA go_apple TYPE REF TO zcl_apple.
DATA go_pear TYPE REF TO zcl_pear.
"第1种强制向下转型,如果子类经过向上转型到父类,则父类对象可以直接强制向下转型。
"强制向下转型,采用 “?=”进行赋值
" 先创建对象
CREATE OBJECT go_vegetarian_food TYPE zcl_vegetarian_food.
CREATE OBJECT go_apple TYPE zcl_apple.
"向上转型
go_vegetarian_food = go_apple.
"然后用“?=”强制向下转型
TRY.
go_apple ?= go_vegetarian_food.
WRITE: / 'go_apple ?= go_vegetarian_food - Correct'.
CATCH cx_sy_move_cast_error.
WRITE : / 'go_apple ?= go_vegetarian_food - Error'.
ENDTRY.
"第2种强制向下转型,如果子类经过向上转型到父类,则父类对象可以直接强制向下转型
"强制向下转型,采用 “MOVE ? TO”进行赋值
"先创建对象
CREATE OBJECT go_vegetarian_food TYPE zcl_vegetarian_food.
CREATE OBJECT go_pear TYPE zcl_pear.
"向上转型
go_vegetarian_food = go_pear.
"然后用MOVE ?TO 强制向下转型
TRY.
MOVE go_vegetarian_food ?TO go_pear.
WRITE :/ 'Move go_vegetarian_food ?TO go_pear - Correct.'.
CATCH cx_sy_move_cast_error.
WRITE : / 'Move go_vegetarian_food ?TO go_pear - Error'.
ENDTRY.
"第3种强制向下转型,如果子类经过向上转型到父类,则父类对象可以直接强制向下
"强制向下转型,采用“CAST”进行赋值
"先创建对象
CREATE OBJECT go_vegetarian_food TYPE zcl_vegetarian_food.
CREATE OBJECT go_pear TYPE zcl_pear.
"向上转型
go_vegetarian_food = go_pear.
"然后用“CAST”强制向下转型
TRY.
CAST zcl_pear( go_vegetarian_food ).
WRITE :/ 'CAST zcl_pear( go_vegetarian_food ) - Correct'.
CATCH cx_sy_move_cast_error.
WRITE :/ 'CAST zcl_pear( go_vegetarian_food ) - Error '.
ENDTRY.
"第4种强制向下转型,如果父类对象没有经过子类的向上转型,就直接进行强制向下转型,那就会引发错误。
"先创建对象
CREATE OBJECT go_vegetarian_food TYPE zcl_vegetarian_food.
CREATE OBJECT go_pear TYPE zcl_pear.
"没有经过子类的向上转型
"go_vegetarian_food = go_pear.
"此时用“CAST”强制向下转型,会引发错误。
TRY.
CAST zcl_pear( go_vegetarian_food ).
WRITE :/ 'CAST zcl_pear( go_vegetarian_food ) - Correct'.
CATCH cx_sy_move_cast_error.
WRITE :/ 'CAST zcl_pear( go_vegetarian_food ) - Error '.
ENDTRY.
结果:
对象的转型分为两大类,具体如下。
1)子类对象赋值给基类对象,将子类对象作为基类对象使用,称为“向上转型”。
向上转型是指基类类型变量指向子类的对象,使得父类可以访问子类方法,但基类对象不能访问子类对象新增加的属性和方法。
2)基类对象强制赋值给子类对象,将基类对象当作子类对象来使用称为“向下转型”。
向下转型包含如下两种类型。
·自动向下转型:在多态时,先向上转型,父类指向子类,调用类方法时,系统是自动向下转型的,目的是调用子类重定义(Redefine)的方法的逻辑,但并不能访问子类所特有的属性和方法。
·强制向下转型:主要目的是为了能够让父类对象调用子类特定的方法。需要用“?=”操作符显示的强制转型。
向上转型就是特殊到一般,将具体的事物转述为抽象(例如我们可以称“桃子”为“水果”);向下转型就是一般到特殊,将抽象的事物变回具体(例如问具体是什么水果?回答是桃子);向上转型是为了找共同点,向下转型是为了找个体的具体特点。
5.4.2 多态的实现(基于非抽象类)
图5-40所示,在本示例中,我们采用多态,父类用于定义共有的行为方法,而子类根据自身的物料类型不同,设定子类特定的打印规则,定义一个ZCL_SUPER_MATL非抽象父类,然后派生出“原料”子类ZCL_SUB_MATL_RAW、“半成品”子类ZCL_SUB_MATL_SEMI、“成品”子类ZCL_SUB_MATL_FIN。
定义父类属性和方法
定义方法的代码。
zcl_super_matl的类代码:
class ZCL_SUPER_MATL definition
public
create public .
public section.
data MV_MATERIAL_ID type STRING .
methods PRINT_MATERIAL_DESC .
protected section.
private section.
ENDCLASS.
CLASS ZCL_SUPER_MATL IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUPER_MATL->PRINT_MATERIAL_DESC
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method PRINT_MATERIAL_DESC.
write : / '物料号码', mv_material_id.
endmethod.
ENDCLASS.
创建“原料”子类ZCL_SUB_MATL_RAW
继承了父类的方法PRINT_MATERIAL_DESC,重新定义该方法。
ZCL_SUB_MATL_RAW类代码:
class ZCL_SUB_MATL_RAW definition
public
inheriting from ZCL_SUPER_MATL
final
create public .
public section.
methods PRINT_MATERIAL_DESC
redefinition .
protected section.
private section.
ENDCLASS.
CLASS ZCL_SUB_MATL_RAW IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUB_MATL_RAW->PRINT_MATERIAL_DESC
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method PRINT_MATERIAL_DESC.
CALL METHOD SUPER->PRINT_MATERIAL_DESC .
WRITE :/ '供应商号码' , 'VEN0127'.
WRITE :/ '批次号码' , 'MF01RAW209'.
WRITE :/ '采购日期' , sy-datum.
endmethod.
ENDCLASS.
创建“半成品”子类ZCL_SUB_MATL_SEMI
在继承设置中设定父类为ZCL_SUPER_MATL。
ZCL_SUB_MATL_SEMI类代码
class ZCL_SUB_MATL_SEMI definition
public
inheriting from ZCL_SUPER_MATL
final
create public .
public section.
methods PRINT_MATERIAL_DESC
redefinition .
protected section.
private section.
ENDCLASS.
CLASS ZCL_SUB_MATL_SEMI IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_SUB_MATL_SEMI->PRINT_MATERIAL_DESC
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method PRINT_MATERIAL_DESC.
CALL METHOD SUPER->PRINT_MATERIAL_DESC .
WRITE: /'批次号码 ', 'MF01SEM537'.
WRITE: /'重检日期 ', sy-datum.
endmethod.
ENDCLASS.
创建“成品”子类ZCL_SUB_MATL_FIN
在继承设置中设定父类为ZCL_SUPER_MATL。 methods PRINT_MATERIAL_DESC redefinition
增加方法: 在方法PRINT_MATERIAL_TYPE中,添加打印物料类型的代码。
ZCL_SUB_MATL_FIN类代码:
REPORT Z000525.
"定义类对象
DATA go_super_matl TYPE REF TO zcl_super_matl.
DATA go_sub_matl_raw TYPE REF TO zcl_sub_matl_raw.
DATA go_sub_matl_semi TYPE REF TO zcl_sub_matl_semi.
DATA go_sub_matl_fin TYPE REF TO zcl_sub_matl_fin.
"创建类对象
CREATE OBJECT go_super_matl.
CREATE OBJECT go_sub_matl_raw.
CREATE OBJECT go_sub_matl_semi.
CREATE OBJECT go_sub_matl_fin.
"为各个类对象设定物料号码
go_super_matl->mv_material_id = 'MATL001'.
go_sub_matl_raw->mv_material_id = 'RAW001'.
go_sub_matl_semi->mv_material_id = 'SEMI001'.
go_sub_matl_fin->mv_material_id = 'FIN001'.
"测试物料类
WRITE: / '物料:'.
go_super_matl->print_material_desc( ).
"向上转型,测试多态下的原料类
WRITE: /,/ '原料:'.
go_super_matl = go_sub_matl_raw.
go_super_matl->print_material_desc( ).
"向上转型,测试多态下的半成品类
WRITE: /,/ '半成品:'.
go_super_matl = go_sub_matl_semi.
go_super_matl->print_material_desc( ).
"向上转型,测试多态下的成品类
WRITE: /,/ '成品:'.
go_super_matl = go_sub_matl_fin.
go_super_matl->print_material_desc( ).
"强制向下转型,测试成品类自身的方法
WRITE: /,/ '成品:'.
go_sub_matl_fin ?= go_super_matl.
go_sub_matl_fin->print_material_desc( ).
go_sub_matl_fin->print_material_type( ).
四个类激活。父类要在子类之前激活。
调用类代码:
运行结果
转型对象具有如下特点。
1)向上转型对象既可以访问子类继承的属性,也可以使用子类继承的或重写的方法,这是向上转型的目的所在。
2)向上转型对象操作子类继承或重写的方法时,是按照子类对象去调用这些方法的,此时,如果子类重写了父类的该方法,那么程序调用的就是子类重写的方法,而不是父类原来定义的方法。
3)向上转型对象不能直接操作子类新增的属性(转型时会失去这部分属性的定义),也不能直接使用子类新增的方法(失去了一些特定的功能),这是向上转型的代价。
4)可以将对象的向上转型对象再强制转换到一个子类的对象(强制向下转型),这时,该子类对象又具备了子类的所有属性和功能。
应该会有读者觉得向下转型是多此一举:要使用子类对象的方法,先要创建子类对象,然后向上转型为父类对象,然后又将父类对象强制向下转型给子类对象,那么既不向上转型,也不向下转型,直接使用子类对象不就行了吗?
其实,向上转型和向下转型是为了实现多态,而多态是面向对象的重要规则,可以实现很多设计功能。
5.4.3 多态的实现(基于抽象类)
抽象类就是不能用来创建对象实例的类,也就是不能实例化的类。
“水果”就是一个不能直接实例化的抽象概念,我们可以将“水果”定义为抽象类
面向对象中抽象类的定义为“包含抽象方法的类”,这里所谓的抽象方法,就是用abstract修饰的,只能定义方法的名称、参数,而没有任何实现代码的“空方法”。
我们用自然语言描述“花木兰是北朝军民中的一个”或者“花弧是北朝军民中的一个”,都是说得通的。
对于类“北朝军民”这个概念,当然不会有一个实际的人物就名为“北朝军民”,所以这个类是没有直接的实例的,也就是说其应该被定义为不能实例化的抽象类。所以我们设定“北朝军民”为抽象类,再设定花木兰类和花弧类作为“北朝军民”的非抽象的子类,作为可以实例化的人物的子类。
创建北朝军民的抽象类
定义抽象方法
zcl_beichao_civilian类代码
class ZCL_BEICHAO_CIVILIAN definition
public
abstract
create public .
public section.
data MV_NAME type STRING read-only .
data MV_GENDER type STRING read-only .
methods SETUP_PROFILE
importing
!IV_NAME type STRING
!IV_GENDER type STRING .
methods SHOW_CAPACITY
abstract .
methods SELF_INTRODUCE .
protected section.
private section.
ENDCLASS.
CLASS ZCL_BEICHAO_CIVILIAN IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_BEICHAO_CIVILIAN->SELF_INTRODUCE
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SELF_INTRODUCE.
WRITE :/ '我是 ' , mv_name, '性别 ' , mv_gender.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_BEICHAO_CIVILIAN->SETUP_PROFILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NAME TYPE STRING
* | [--->] IV_GENDER TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SETUP_PROFILE.
mv_name = iv_name.
mv_gender = iv_gender.
endmethod.
ENDCLASS.
创建子类huahu
zcl_huahu类代码:
class ZCL_HUAHU definition
public
inheriting from ZCL_BEICHAO_CIVILIAN
final
create public .
public section.
methods SHOW_CAPACITY
redefinition .
protected section.
private section.
ENDCLASS.
CLASS ZCL_HUAHU IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_HUAHU->SHOW_CAPACITY
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SHOW_CAPACITY.
WRITE : / '年老体衰, 耕读传家' .
endmethod.
ENDCLASS.
创建子类huamulan
zcl_huamulan类代码
class ZCL_HUAMULAN definition
public
inheriting from ZCL_BEICHAO_CIVILIAN
final
create public .
public section.
methods MAKE_UP .
methods SHOW_CAPACITY
redefinition .
protected section.
private section.
ENDCLASS.
CLASS ZCL_HUAMULAN IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_HUAMULAN->MAKE_UP
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method MAKE_UP.
WRITE :/ '当窗理云鬓, 对镜贴花黄'.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_HUAMULAN->SHOW_CAPACITY
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method SHOW_CAPACITY.
WRITE : / '年轻力壮, 冲锋陷阵'.
endmethod.
ENDCLASS.
调用类代码:
*&---------------------------------------------------------------------*
*& Report Z000532
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000532.
DATA :
"declare super class
go_civi_solider TYPE REF TO zcl_beichao_civilian,
"declare sub class
go_hua_hu TYPE REF TO zcl_huahu,
"declare sub class
go_hua_mulan TYPE REF TO zcl_huamulan.
START-OF-SELECTION.
"1.创建花木兰的对象实例
CREATE OBJECT go_hua_mulan.
WRITE : /, / '1. 测试子类对象花木兰:'.
"自我介绍所用的是花木兰的信息,我是花木兰,女
CALL METHOD go_hua_mulan->setup_profile
EXPORTING
iv_name = '花木兰'
iv_gender = '女'.
CALL METHOD go_hua_mulan->self_introduce.
"北朝人尚武,女孩子也会武术
CALL METHOD go_hua_mulan->show_capability.
"当然女孩也能梳妆打扮
CALL METHOD go_hua_mulan->make_up.
"2. 创建花弧的对象实例
CREATE OBJECT go_hua_hu.
WRITE : /, / '2. 测试子类对象花弧:'.
"调用的是父类的自我介绍:我是花弧,男
CALL METHOD go_hua_hu->setup_profile
EXPORTING
iv_name = '花弧'
iv_gender = '男'.
CALL METHOD go_hua_hu->self_introduce.
"无奈年老体衰,力不从心
CALL METHOD go_hua_hu->show_capability.
"父类没有子类的打扮能力
"CALL METHOD go_hua_hu->make_up.
"3. 测试多态下花弧的能力
WRITE : /, / '3. 测试子类对象向上转型到父类对象,多态 - 父亲无法打仗:'.
"父类是虚拟类,不能直接创建对象, 但是可以依据实现所有方法的子类类型创建对象
"CREATE OBJECT go_civi_solider TYPE zcl_huahu.
"虚拟类对象变量不经创建,直接由子类对象赋值即可
go_civi_solider = go_hua_hu.
"向上转型,对象是go_civi_solider,
"此时是查看子类对象花弧的能力
go_civi_solider = go_hua_hu.
"在军中报名: 调用的是父类的自我介绍“我是花弧,男”
CALL METHOD go_civi_solider->self_introduce.
"如果花弧在军中打仗: 花弧不能负担作战任务,只能充当牺牲品
CALL METHOD go_civi_solider->show_capability.
"4. 花木兰决定代父从军
WRITE : /, / '4. 花木兰决定代父从军'.
"子类花木兰声称自己就是花弧,准备以父亲的名义参军
CALL METHOD go_hua_mulan->setup_profile
EXPORTING
iv_name = '花弧'
iv_gender = '男'.
" 5.将花木兰对象实例向上转型
WRITE : /, / '5. 测试将子类对象向上转型到父类对象,多态 - 代父从军:'.
"向上转型,对象是go_civi_solider,名义上是花弧在参军,其实是花木兰女扮男装代父从军
"向上转型后,对象go_hua_mulan和go_civi_solider指向的是同一个内存区域
go_civi_solider = go_hua_mulan.
"在军中报名: 调用的是重新声明过的自我介绍“我是花弧,男”
CALL METHOD go_civi_solider->self_introduce.
"在军中打仗: 打仗的时候,其实上阵打仗的是花木兰,打仗的功夫也是花木兰自己的能力
CALL METHOD go_civi_solider->show_capability.
"多态的代价是不能调用子类新增加的方法,军中也不允许花木兰使用自己新增加的方法
"CALL METHOD go_civi_solider->make_up.
" 6.花木兰回乡团聚
WRITE : /, / '6. 测试父类对象强制向下转型到子类对象, 花木兰重新做回自己:'.
"不必按子类类型重新创建父类对象, 因为子类曾经向上转型,强制向下转型可以直接进行
"强制向下转型,花木兰回家后重新过上女儿的生活
go_hua_mulan ?= go_civi_solider.
"花木兰回家,重新声明自己其实是作为女儿的花木兰
CALL METHOD go_hua_mulan->setup_profile
EXPORTING
iv_name = '花木兰'
iv_gender = '女'.
"自我介绍用的是十年后的花木兰的信息
CALL METHOD go_hua_mulan->self_introduce.
"花木兰依然具有冲锋打仗的能力, 如有战事依然能够上阵
CALL METHOD go_hua_mulan->show_capability.
"强制向下转型后,可以重新调用子类新增加的方法
"花木兰终于可以开始使用自己梳妆打扮的能力
CALL METHOD go_hua_mulan->make_up.
结果
在使用虚拟类时需要注意以下几点。
1)抽象类不能被实例化,抽象类是用来约束和定义子类的,而子类才是实例化后处理业务的骨干。
2)抽象方法是不包含任何代码的,只能定义方法名称和参数,代码必须由子类来进行重写,以实现父类的抽象方法。
3)类中只要包含一个抽象方法,该类就必须定义为抽象类,而无论该类是否包含有其他非抽象方法。
4)如果类中所有的方法都是非抽象的,那么该类依然可以被定义为一个抽象类,例如不用实例化的一些计算类。
5)类的实例化类型设定中,Abstract不能与Public、Protected、Private并列修饰同一个类,也就是说这几个类型的设定是互斥的(详见第4章)。
6)类方法的设定中,Abstract不能与Private、Static、Final并列修饰同一个方法,也就是说这几个的设定是互斥的。
原因1:抽象方法不应该被定义成私有(Private)的,因为若为私有的,就无法被子类继承了。编译时也会报错“Private methods cannot be redefined,and they may therefore not be declared"ABSTRACT".)”。抽象方法的可见度应该是Public或者Protected,能够让子类继承。所以Abstract和Private是互斥的。
原因2:我们知道,非私有的静态方法可以被子类继承,但不可以被子类覆盖重写(否则会报错“Staticmethods cannot be redefined”),因为静态方法是类和对象所共享的,继承后父类和子类也会共享静态方法。
同样,抽象方法不应该被定义成静态的,因为静态方法本身是无法被子类覆盖重写的。即便抽象方法被设定为子类无法继承的私有的,编译时也会报错“You cannot redefine static methods,and they may thereforenot be declared"ABSTRACT".”。所以Abstract与Static是互斥的。
原因3:抽象方法不能设定为最终方法Final,也就是说,Abstract不能与Final并列修饰同一个方法,因为一旦声明为虚拟方法,就是为了子类继承而实现的。所以Abstract与Final是互斥的。
5.4.4 多态的小结
优点具
1)程序的可复用性较好。
2)可扩充性和可维护性较好。比如将物料类设定为抽象类,定义了原料、半成品、成品为子类,当出现一个新的物料类型如“样品类型”时,我们就需要再从父类中继承创建一个新类型即可,不必对原料、半成品、成品类型进行修改,调用程序的修改也较少且很有规律。
3)多态的语法比较清晰简洁,只要向上转型,对所有子类方法的调用就都是由父类对象直接调用的,一致而规律。
运行时多态通常有两种实现方法:
1)继承多态,子类继承父类(extends)。
2)接口多态,类实现接口(implements)。
本章讲解的就是继承多态,无论是哪种方法,其核心之处都在于对父类方法的改写或对接口方法的实现,以在运行时取得不同的执行效果。
要使用多态,声明时总是对父类类型或者接口类型,调用时创建的对象是实际类型,通过向上转型动态调用。
封装、继承和多态,基本的思路具体如下。
·封装可以隐藏实现细节,使得代码模块化。
·继承可以扩展已存在的类,来保证代码重用,并且是多态的基础。
·而多态则是在继承的基础上,实现父类和子类调用逻辑的重用,也就是业务接口的重用,提高系统的可复用性。
·在基于类的多态时,我们推荐使用抽象类作为基类。
5.5 接口
5.5.1 接口的概述
简单地说,接口只有一组方法的声明,而没有定义方法的实现,只说明了做什么,但没有定义具体应该怎么做。
并不是说系统的复杂性因为使用了接口就由此得到了彻底的解决,但较之前面各自为战的混乱局面来说,接口的意义不只是改良,简直是一种彻底的进步。
5.5.2 接口的实现
为一家航空公司开发网络订票系统,让所有的合作客户都可以根据规则来查询核心航班系统的相关数据。
此处我们创建一个接口,并声明接口的方法GET_FLIGHT_DETAILS,该方法是用于获取航班详细信息的方法。
示例业务中需要实现的是一种查询能力,票务公司想查询的是价格,旅行社想查询的是起降地点,实现类可以根据自己的需要实现这个方法,当不同的类调用这个接口的方法时,该接口的方法就会根据实现类的具体逻辑执行,这样的业务就可以用接口来进行定义。
创建接口
接口类代码:
interface ZIF_GET_FLIGHT
public .
methods GET_FLIGHT_DETAILS
importing
!IV_FLIGHT_NO type SFLIGHT-CONNID
exporting
!ES_SFLIGHT type SFLIGHT
!ES_SFLIGHTS type SFLIGHTS .
endinterface.
创建实现类
zcl_get_flight_price类代码
class ZCL_GET_FLIGHT_PRICE definition
public
final
create public .
public section.
interfaces ZIF_GET_FLIGHT .
protected section.
private section.
ENDCLASS.
CLASS ZCL_GET_FLIGHT_PRICE IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GET_FLIGHT_PRICE->ZIF_GET_FLIGHT~GET_FLIGHT_DETAILS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_FLIGHT_NO TYPE SFLIGHT-CONNID
* | [<---] ES_SFLIGHT TYPE SFLIGHT
* | [<---] ES_SFLIGHTS TYPE SFLIGHTS
* +--------------------------------------------------------------------------------------</SIGNATURE>
method ZIF_GET_FLIGHT~GET_FLIGHT_DETAILS.
* get flight price from sflight table.
SELECT SINGLE carrid connid fldate price currency
FROM sflight
INTO CORRESPONDING FIELDS OF es_sflight
WHERE connid = iv_flight_no.
endmethod.
ENDCLASS.
创建实现类zcl_get_flight_dest:
实现类zcl_get_flight_dest代码:
class ZCL_GET_FLIGHT_DEST definition
public
final
create public .
public section.
interfaces ZIF_GET_FLIGHT .
methods PRINT_FLIGHT_DEST .
protected section.
private section.
ENDCLASS.
CLASS ZCL_GET_FLIGHT_DEST IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GET_FLIGHT_DEST->PRINT_FLIGHT_DEST
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
method PRINT_FLIGHT_DEST.
WRITE : / 'print_flight_dest'.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_GET_FLIGHT_DEST->ZIF_GET_FLIGHT~GET_FLIGHT_DETAILS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_FLIGHT_NO TYPE SFLIGHT-CONNID
* | [<---] ES_SFLIGHT TYPE SFLIGHT
* | [<---] ES_SFLIGHTS TYPE SFLIGHTS
* +--------------------------------------------------------------------------------------</SIGNATURE>
method ZIF_GET_FLIGHT~GET_FLIGHT_DETAILS.
"get flight departure and arrival city
SELECT SINGLE carrid carrname connid countryfr cityfrom airpfrom
countryto cityto airpto
FROM sflights
INTO CORRESPONDING FIELDS OF es_sflights
WHERE connid = iv_flight_no.
endmethod.
ENDCLASS.
调用类:
*&---------------------------------------------------------------------*
*& Report Z000536
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z000536.
DATA:
sflight TYPE sflight.
* object define.
DATA:
gi_get_flights TYPE REF TO zif_get_flight,
go_flight_p TYPE REF TO zcl_get_flight_price,
go_flight_d TYPE REF TO zcl_get_flight_dest,
gs_flights TYPE sflight,
gs_sflights TYPE sflights.
PARAMETERS: s_connid TYPE sflight-connid.
* create object
START-OF-SELECTION.
CREATE OBJECT go_flight_p.
* call interface method.
CALL METHOD go_flight_p->zif_get_flight~get_flight_details
EXPORTING
iv_flight_no = s_connid
IMPORTING
es_sflight = gs_flights.
* display the price data.
IF sy-subrc = 0.
cl_demo_output=>display_data( gs_flights ).
ENDIF.
" upcasting
CREATE OBJECT gi_get_flights TYPE zcl_get_flight_dest.
* call interface method.
call METHOD gi_get_flights->get_flight_details
EXPORTING
iv_flight_no = s_connid
IMPORTING
es_sflights = gs_sflights.
* display the departure and arrival city data
IF sy-subrc = 0.
cl_demo_output=>display_data( gs_sflights ).
ENDIF.
* method "print_flight_dest" is unknow or protected or private.
* call methid gi_get_flights->print_flight_dest( ).
* down casting.
go_flight_d ?= gi_get_flights.
CALL METHOD go_flight_d->print_flight_dest( ).
继承概念的实现方式有两种:实现继承和接口继承。
·实现继承是指子类直接使用父类的属性和方法,不需要进行额外的编码。
·接口继承是指接口定义功能,实现类(子类)使用接口的属性和方法的名称,但是子类必须实现父类的方法。
一般来说,对于实际的工程技术实现,我们推荐使用接口继承。
ABAP OOP的接口和抽象类比较相近,但却不是同一个概念。