Перейти до вмісту

Класи та об'єкти

ABAP Objects — обʼєктно-орієнтована частина мови: класи, інтерфейси, спадкування, поліморфізм. У сучасному ABAP (особливо в ABAP Cloud) бізнес-логіка пишеться у класах — процедурні програми і function modules залишаються для legacy та тонких шарів інтеграції.

Клас — шаблон. На ньому будується екземпляр (instance) з власним станом. Клас складається з двох частин: DEFINITION (оголошення компонентів) і IMPLEMENTATION (тіла методів).

Локальний vs глобальний клас

Section titled “Локальний vs глобальний клас”
ТипДе живеОбласть видимості
Local classвсередині ABAP-програми (CCIMP, report)тільки ця програма
Global classclass pool як repository objectвсюди, де видно

Якщо клас потрібен лише в одній програмі — починай з local. Global робиш тоді, коли його використовуватимуть інші програми. Global маркується додатком PUBLIC у CLASS ... DEFINITION PUBLIC.

" Оголошення
CLASS zcl_demo DEFINITION
PUBLIC " робить клас глобальним
FINAL " заборона спадкування
CREATE PUBLIC. " екземпляр можна створювати будь-де
PUBLIC SECTION.
" публічні компоненти
PROTECTED SECTION.
" видимі у підкласах
PRIVATE SECTION.
" видимі тільки всередині цього класу
ENDCLASS.
" Реалізація
CLASS zcl_demo IMPLEMENTATION.
" тіла методів
ENDCLASS.

Додатки до DEFINITION:

ДодатокЩо робить
PUBLICробить клас глобальним
FINALзабороняє успадкування
ABSTRACTклас не можна інстанціювати; може містити abstract методи
CREATE PUBLICінстанціювати можна будь-де (за замовчуванням)
CREATE PROTECTEDтільки у підкласах, самому класі, friends
CREATE PRIVATEтільки у самому класі або friends (патерн factory method)
INHERITING FROM superclassуспадкування від суперкласу
FOR TESTINGтестовий клас ABAP Unit
GLOBAL FRIENDS classнадає friend-доступ іншому класу

Три секції видимості забезпечують інкапсуляцію:

Доступ ізpublicprotectedprivate
Цей клас і його friendsтактактак
Підкласитактакні
Будь-хто ззовнітакніні
CLASS zcl_demo DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
DATA inst_attr TYPE i. " instance-атрибут
CLASS-DATA static_attr TYPE string. " static-атрибут (один на клас)
CONSTANTS c_pi TYPE p DECIMALS 5 VALUE '3.14159'.
TYPES ty_name TYPE c LENGTH 40.
PRIVATE SECTION.
DATA counter TYPE i.
ENDCLASS.

CLASS-DATA існує один раз для всього класу — доступ через zcl_demo=>static_attr. DATA існує у кожному екземплярі — через ref->inst_attr.

READ-ONLY дозволяє читати атрибут ззовні, але писати — тільки зсередини класу:

DATA id TYPE i READ-ONLY.
CLASS zcl_demo DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS calc
IMPORTING a TYPE i
b TYPE i DEFAULT 0
text TYPE string OPTIONAL
EXPORTING log TYPE string
CHANGING counter TYPE i
RETURNING VALUE(result) TYPE i
RAISING cx_my_exc.
CLASS-METHODS greet IMPORTING name TYPE string.
ENDCLASS.

Типи параметрів:

  • IMPORTING — вхід; за замовчуванням by reference (read-only усередині).
  • EXPORTING — вихід.
  • CHANGING — і вхід, і вихід.
  • RETURNING VALUE(r) — єдине значення; робить метод функціональним (можна використовувати у виразах).
  • RAISING — виняток, який метод може кинути.

VALUE(...) перед іменем — передача за значенням (by value). RETURNING завжди by value.

" Функціональний метод — у виразі
DATA(sum) = obj->calc( a = 1 b = 2 ).
" Коли параметр один — можна без імені
DATA(len) = helper->length( `text` ).
" Метод без RETURNING
obj->process( EXPORTING x = 1 IMPORTING y = DATA(y) ).
" Static-метод
zcl_demo=>greet( `World` ).

Два види: instance і static.

CLASS zcl_demo DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
" Instance-конструктор — викликається при NEW
METHODS constructor IMPORTING name TYPE string.
" Static-конструктор — викликається один раз при першому доступі до класу
CLASS-METHODS class_constructor.
ENDCLASS.
CLASS zcl_demo IMPLEMENTATION.
METHOD constructor.
me->name = name.
ENDMETHOD.
METHOD class_constructor.
" Ініціалізація CLASS-DATA
ENDMETHOD.
ENDCLASS.

Static-конструктор запускається автоматично при першому зверненні до класу (перший NEW, виклик static-методу, доступ до static-атрибута). Вручну викликати не можна.

" Оголошення reference-змінної
DATA ref TYPE REF TO zcl_demo.
" Створення інстанса
ref = NEW #( name = `demo` ).
" Inline — компактно
DATA(ref2) = NEW zcl_demo( name = `demo` ).

Оператор NEW — сучасний спосіб. Старіший CREATE OBJECT ref EXPORTING ... ще працює, але не треба.

Усередині instance-методу me — посилання на поточний екземпляр. Потрібне, коли треба розрізнити атрибут і локальну змінну з однаковим іменем:

METHOD constructor.
me->name = name. " me->name — атрибут; name — параметр
ENDMETHOD.

Якщо метод повертає обʼєкт — виклики можна чейнити:

DATA(result) = factory=>create( )->configure( `x` )->build( ).
" Так само для атрибутів
DATA(val) = obj->sub_obj->field.
CLASS lcl_animal DEFINITION.
PUBLIC SECTION.
METHODS: constructor IMPORTING name TYPE string,
speak RETURNING VALUE(sound) TYPE string.
PROTECTED SECTION.
DATA name TYPE string.
ENDCLASS.
CLASS lcl_dog DEFINITION INHERITING FROM lcl_animal.
PUBLIC SECTION.
METHODS speak REDEFINITION.
ENDCLASS.
CLASS lcl_dog IMPLEMENTATION.
METHOD speak.
sound = |{ name } гавкає|.
ENDMETHOD.
ENDCLASS.

Правила:

  • ABAP підтримує лише одиничне спадкування (один суперклас).
  • Усі класи неявно успадковують від object.
  • REDEFINITION перевизначає метод суперкласу. Сигнатура зберігається.
  • FINAL METHODS ... — заборона перевизначати конкретний метод.
  • ABSTRACT METHODS ... — метод без тіла, підклас зобовʼязаний реалізувати.

Виклик методу суперкласу

Section titled “Виклик методу суперкласу”
METHOD speak.
DATA(base_sound) = super->speak( ).
sound = |{ base_sound } і гавкає|.
ENDMETHOD.

Конструктор у підкласі

Section titled “Конструктор у підкласі”
CLASS lcl_dog DEFINITION INHERITING FROM lcl_animal.
PUBLIC SECTION.
METHODS constructor IMPORTING name TYPE string breed TYPE string.
PRIVATE SECTION.
DATA breed TYPE string.
ENDCLASS.
CLASS lcl_dog IMPLEMENTATION.
METHOD constructor.
super->constructor( name = name ). " обовʼязково першим
me->breed = breed.
ENDMETHOD.
ENDCLASS.

Посилання на суперклас може вказувати на підклас — це основа поліморфізму:

DATA animal TYPE REF TO lcl_animal.
animal = NEW lcl_dog( name = `Rex` breed = `husky` ).
DATA(sound) = animal->speak( ). " викличеться lcl_dog->speak
" Downcast — отримати посилання конкретного типу
DATA dog TYPE REF TO lcl_dog.
dog ?= animal. " через оператор ?=
DATA(dog2) = CAST lcl_dog( animal ). " те саме через CAST
IF animal IS INSTANCE OF lcl_dog.
DATA(dog) = CAST lcl_dog( animal ).
" ...
ENDIF.
CLASS lcl_shape DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS area ABSTRACT RETURNING VALUE(r) TYPE f.
ENDCLASS.
" NEW lcl_shape( ) — помилка компіляції

Абстрактний клас не можна інстанціювати. Абстрактні методи реалізуються у підкласах через REDEFINITION.

Інтерфейс — чистий контракт без реалізації. Клас може реалізовувати кілька інтерфейсів (на відміну від одиничного спадкування):

INTERFACE lif_serializable.
METHODS to_json RETURNING VALUE(json) TYPE string.
ENDINTERFACE.
INTERFACE lif_named.
DATA name TYPE string.
METHODS set_name IMPORTING n TYPE string.
ENDINTERFACE.
CLASS lcl_user DEFINITION.
PUBLIC SECTION.
INTERFACES: lif_serializable, lif_named.
ENDCLASS.
CLASS lcl_user IMPLEMENTATION.
METHOD lif_serializable~to_json.
json = |\{"name":"{ lif_named~name }"\}|.
ENDMETHOD.
METHOD lif_named~set_name.
lif_named~name = n.
ENDMETHOD.
ENDCLASS.

Методи інтерфейсу завжди адресуються через ~: lif_serializable~to_json. Це важливо, коли два інтерфейси мають методи з однаковим іменем.

Робота через посилання на інтерфейс

Section titled “Робота через посилання на інтерфейс”
DATA srl TYPE REF TO lif_serializable.
srl = NEW lcl_user( ).
DATA(json) = srl->to_json( ). " без ~, бо srl вже типу інтерфейса

FRIENDS дає іншому класу повний доступ до всіх компонентів, включно з private:

CLASS zcl_secret DEFINITION
PUBLIC FINAL CREATE PUBLIC
GLOBAL FRIENDS zcl_trusted.
PRIVATE SECTION.
DATA secret TYPE string.
ENDCLASS.
" zcl_trusted тепер може читати/писати secret

Для local — CLASS zcl_main DEFINITION LOCAL FRIENDS ltc_test. — класичний патерн для unit-тестів, щоб тест бачив private-методи класу.

ABAP має вбудовану підтримку publisher-subscriber через events:

CLASS lcl_button DEFINITION.
PUBLIC SECTION.
EVENTS click EXPORTING VALUE(x) TYPE i.
METHODS press.
ENDCLASS.
CLASS lcl_button IMPLEMENTATION.
METHOD press.
RAISE EVENT click EXPORTING x = 42.
ENDMETHOD.
ENDCLASS.
CLASS lcl_handler DEFINITION.
PUBLIC SECTION.
METHODS on_click FOR EVENT click OF lcl_button IMPORTING x.
ENDCLASS.
" Реєстрація
DATA(btn) = NEW lcl_button( ).
DATA(h) = NEW lcl_handler( ).
SET HANDLER h->on_click FOR btn.
btn->press( ). " викличе on_click

Достроковий вихід з методу. Якщо є RETURNING VALUE(r) — поверне поточне значення r:

METHOD find.
LOOP AT itab INTO DATA(line).
IF line-key = key.
result = line.
RETURN. " вийти з методу одразу
ENDIF.
ENDLOOP.
ENDMETHOD.

Шаблон мінімального сервісного класу

Section titled “Шаблон мінімального сервісного класу”
CLASS zcl_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS: constructor IMPORTING dep TYPE REF TO lif_repository,
run IMPORTING id TYPE i
RETURNING VALUE(result) TYPE string
RAISING cx_service_error.
PRIVATE SECTION.
DATA repo TYPE REF TO lif_repository.
ENDCLASS.
CLASS zcl_service IMPLEMENTATION.
METHOD constructor.
me->repo = dep.
ENDMETHOD.
METHOD run.
DATA(entity) = repo->get( id ).
result = |processed: { entity-name }|.
ENDMETHOD.
ENDCLASS.

Dependency injection через конструктор, посилання на інтерфейс, один публічний метод — типова одиниця сервісу в ABAP Cloud.

Адаптовано з 04_ABAP_Object_Orientation.md (Apache 2.0). Повний перелік нюансів — в оригіналі.