ABAP OOP 실전 — CLASS, INTERFACE, INHERITANCE 완전 정복

Moderator · 조회 2

1. 개요 - ABAP OOP가 왜 중요한가

SAP S/4HANA와 BTP(Business Technology Platform) 시대에 접어들면서, ABAP 개발은 더 이상 절차적 프로그래밍만으로는 충분하지 않습니다. RAP(RESTful ABAP Programming Model), Clean ABAP, ABAP Cloud 등 모던 ABAP 개발 패러다임은 모두 객체지향(OOP) 기반 위에 설계되어 있습니다.

이 튜토리얼에서는 ABAP OOP의 세 가지 핵심 축인 CLASS, INTERFACE, INHERITANCE를 체계적으로 다룹니다.

학습 대상: ABAP 기본 문법(DATA, TYPES, SELECT 등)을 알고 있으며, 객체지향 개발로 전환하려는 ABAP 개발자. SAP S/4HANA 2021 이상 또는 SAP BTP ABAP Environment에서 실습할 수 있습니다.

2. CLASS 기본 구조 - 선언부와 구현부

ABAP 클래스는 크게 선언부(DEFINITION)구현부(IMPLEMENTATION)로 나뉩니다. 선언부에서는 클래스가 어떤 속성과 메서드를 가지는지 정의하고, 구현부에서는 메서드의 실제 로직을 작성합니다.

로컬 클래스 vs 글로벌 클래스

구분 로컬 클래스 글로벌 클래스
위치 프로그램/인클루드 내부 SE24 또는 ADT에서 별도 생성
접근 범위 해당 프로그램 내에서만 시스템 전체에서 사용 가능
선언 키워드 CLASS lcl DEFINITION. CLASS zcl DEFINITION PUBLIC.
용도 테스트, 헬퍼, 로컬 로직 재사용 가능한 비즈니스 로직

로컬 클래스의 기본 구조는 다음과 같습니다.

CLASS lcl_calculator DEFINITION.
  PUBLIC SECTION.
    METHODS: add IMPORTING iv_a TYPE i
                           iv_b TYPE i
                 RETURNING VALUE(rv_result) TYPE i.

  PRIVATE SECTION.
    DATA: mv_last_result TYPE i.
ENDCLASS.

CLASS lcl_calculator IMPLEMENTATION.
  METHOD add.
    rv_result = iv_a + iv_b.
    mv_last_result = rv_result.
  ENDMETHOD.
ENDCLASS.

글로벌 클래스는 추가로 CREATE PUBLIC, FINAL, ABSTRACT 등의 옵션을 지정할 수 있습니다.

핵심: 선언부(DEFINITION)는 "무엇을 할 수 있는가"를, 구현부(IMPLEMENTATION)는 "어떻게 하는가"를 담당합니다. 이 분리가 ABAP OOP의 기본 원칙입니다.

3. 가시성과 속성 - PUBLIC / PROTECTED / PRIVATE

클래스의 컴포넌트(속성, 메서드)는 가시성 섹션에 따라 접근 범위가 결정됩니다. 이것은 캡슐화(Encapsulation)의 핵심 메커니즘입니다.

섹션 클래스 내부 서브클래스 외부 호출자 Friend
PUBLIC O O O O
PROTECTED O O X O
PRIVATE O X X O

인스턴스 속성 vs 정적 속성

비유하자면, 인스턴스 속성은 "각 직원의 개인 사물함"이고, 정적 속성은 "부서 공용 게시판"과 같습니다. 모든 직원(인스턴스)이 각자의 사물함을 갖지만, 게시판은 부서(클래스) 하나에 하나만 존재합니다.

설계 원칙: 가능한 한 PRIVATE으로 선언하고, 필요한 것만 PUBLIC으로 노출하세요. "최소 권한 원칙"은 유지보수성과 안정성을 높여줍니다.

4. 메서드와 생성자

메서드는 클래스의 행위(Behavior)를 정의합니다. ABAP OOP에서는 인스턴스 메서드정적 메서드, 그리고 두 종류의 생성자를 구분해야 합니다.

파라미터 종류

키워드 방향 설명
IMPORTING 입력 호출자가 메서드에 전달하는 값
EXPORTING 출력 메서드가 호출자에게 반환하는 값
CHANGING 입출력 전달받아 변경 후 반환
RETURNING 반환 함수형 메서드, 메서드 체이닝에 필수
RAISING 예외 발생 가능한 예외 클래스 선언

생성자(Constructor)

CLASS lcl_employee DEFINITION.
  PUBLIC SECTION.
    METHODS constructor IMPORTING iv_name   TYPE string
                                  iv_emp_id TYPE i.
    CLASS-METHODS class_constructor.
    METHODS get_info RETURNING VALUE(rv_info) TYPE string.

  PRIVATE SECTION.
    DATA: mv_name   TYPE string,
          mv_emp_id TYPE i.
    CLASS-DATA: sv_counter TYPE i.
ENDCLASS.

CLASS lcl_employee IMPLEMENTATION.
  METHOD class_constructor.
    sv_counter = 0.
    " 클래스 최초 사용 시 1회 실행 (예: 로그 초기화)
  ENDMETHOD.

  METHOD constructor.
    mv_name   = iv_name.
    mv_emp_id = iv_emp_id.
    sv_counter = sv_counter + 1.
  ENDMETHOD.

  METHOD get_info.
    rv_info = |사원번호: { mv_emp_id } / 이름: { mv_name } / 전체 직원 수: { sv_counter }|.
  ENDMETHOD.
ENDCLASS.
주의: class_constructor는 파라미터를 받을 수 없고, 반드시 PUBLIC SECTION에 선언해야 합니다. 정적 속성의 초기화나 환경 설정 로딩에 주로 사용됩니다.

5. 객체 생성과 사용 - NEW, ->, =>

ABAP 7.40 이후부터 NEW 연산자인라인 선언을 활용하여 간결하게 객체를 생성할 수 있습니다.

객체 생성 방법

" 방법 1: 전통적 방식
DATA lo_emp TYPE REF TO lcl_employee.
CREATE OBJECT lo_emp
  EXPORTING iv_name   = '김개발'
            iv_emp_id = 1001.

" 방법 2: NEW 연산자 (ABAP 7.40+)
DATA lo_emp2 TYPE REF TO lcl_employee.
lo_emp2 = NEW #( iv_name = '박설계' iv_emp_id = 1002 ).

" 방법 3: 인라인 선언 + NEW (가장 간결)
DATA(lo_emp3) = NEW lcl_employee( iv_name = '이분석' iv_emp_id = 1003 ).

" 인스턴스 멤버 접근: -> 사용
DATA(lv_info) = lo_emp3->get_info( ).

" 정적 멤버 접근: => 사용
DATA(lv_count) = lcl_employee=>sv_counter.

self-reference: me

인스턴스 메서드 내부에서 me->를 통해 자기 자신의 속성이나 메서드를 명시적으로 참조할 수 있습니다. 로컬 변수와 인스턴스 속성의 이름이 동일할 때 구분하는 데 유용합니다.

메서드 체이닝

RETURNING 파라미터가 자기 자신의 레퍼런스를 반환하면, 메서드를 연속으로 호출하는 체이닝 패턴을 구현할 수 있습니다. 빌더(Builder) 패턴 등에서 자주 활용됩니다.

권장: 모던 ABAP 개발에서는 NEW 연산자와 인라인 선언을 사용하는 것이 일반적입니다. CREATE OBJECT는 레거시 코드에서 주로 볼 수 있습니다.

6. 상속(INHERITANCE) - 코드 재사용과 특화

상속은 기존 클래스(슈퍼클래스)의 속성과 메서드를 물려받아 새로운 클래스(서브클래스)를 만드는 메커니즘입니다. INHERITING FROM 키워드로 상속 관계를 선언합니다.

상속 규칙 요약

" 슈퍼클래스: 일반 문서
CLASS lcl_document DEFINITION.
  PUBLIC SECTION.
    METHODS constructor IMPORTING iv_title TYPE string.
    METHODS get_description RETURNING VALUE(rv_desc) TYPE string.

  PROTECTED SECTION.
    DATA: mv_title      TYPE string,
          mv_created_at TYPE timestamp.
ENDCLASS.

CLASS lcl_document IMPLEMENTATION.
  METHOD constructor.
    mv_title = iv_title.
    GET TIME STAMP FIELD mv_created_at.
  ENDMETHOD.

  METHOD get_description.
    rv_desc = |문서: { mv_title }|.
  ENDMETHOD.
ENDCLASS.

" 서브클래스: 승인 문서 (상속 + 확장)
CLASS lcl_approval_doc DEFINITION INHERITING FROM lcl_document.
  PUBLIC SECTION.
    METHODS constructor IMPORTING iv_title    TYPE string
                                  iv_approver TYPE string.
    METHODS get_description REDEFINITION.

  PRIVATE SECTION.
    DATA: mv_approver TYPE string,
          mv_status   TYPE string VALUE 'PENDING'.
ENDCLASS.

CLASS lcl_approval_doc IMPLEMENTATION.
  METHOD constructor.
    super->constructor( iv_title = iv_title ).  " 슈퍼클래스 생성자 호출 필수
    mv_approver = iv_approver.
  ENDMETHOD.

  METHOD get_description.
    rv_desc = |승인문서: { mv_title } / 승인자: { mv_approver } / 상태: { mv_status }|.
  ENDMETHOD.
ENDCLASS.

" 사용 예시
DATA(lo_doc) = NEW lcl_approval_doc( iv_title = '구매요청서' iv_approver = '김부장' ).
DATA(lv_desc) = lo_doc->get_description( ).
" 결과: 승인문서: 구매요청서 / 승인자: 김부장 / 상태: PENDING

업캐스트와 다운캐스트

업캐스트(Upcast)는 서브클래스 레퍼런스를 슈퍼클래스 레퍼런스에 할당하는 것으로, 자동으로 수행됩니다. 반면 다운캐스트(Downcast)는 슈퍼클래스 레퍼런스를 서브클래스 레퍼런스로 변환하는 것이며, 반드시 CAST 연산자를 사용해야 합니다.

" 업캐스트: 서브 -> 슈퍼 (자동)
DATA lo_base TYPE REF TO lcl_document.
DATA(lo_approval) = NEW lcl_approval_doc( iv_title = '견적서' iv_approver = '이팀장' ).
lo_base = lo_approval.  " 업캐스트 - 자동 변환

" 다운캐스트: 슈퍼 -> 서브 (명시적 CAST 필요)
TRY.
    DATA(lo_back) = CAST lcl_approval_doc( lo_base ).
    DATA(lv_info) = lo_back->get_description( ).
  CATCH cx_sy_move_cast_error INTO DATA(lx_error).
    " 캐스팅 실패 시 예외 처리
    DATA(lv_msg) = lx_error->get_text( ).
ENDTRY.

" IS INSTANCE OF 로 안전한 타입 검사
IF lo_base IS INSTANCE OF lcl_approval_doc.
  " 안전하게 다운캐스트 가능
ENDIF.
실무 팁: 다운캐스트 전에는 항상 IS INSTANCE OF 또는 TRY-CATCH를 사용하세요. 런타임에 CX_SY_MOVE_CAST_ERROR 예외가 발생할 수 있습니다.

7. 인터페이스(INTERFACE) - 계약과 다형성

인터페이스는 "무엇을 해야 하는가"만 정의하고, "어떻게 하는가"는 구현 클래스에 맡기는 계약(Contract)입니다. ABAP 인터페이스의 주요 특징은 다음과 같습니다.

" 인터페이스 정의
INTERFACE lif_printable.
  METHODS print RETURNING VALUE(rv_output) TYPE string.
ENDINTERFACE.

INTERFACE lif_exportable.
  METHODS export_to_csv RETURNING VALUE(rv_csv) TYPE string.
ENDINTERFACE.

" 두 개의 인터페이스를 동시에 구현하는 클래스
CLASS lcl_sales_order DEFINITION.
  PUBLIC SECTION.
    INTERFACES: lif_printable,
                lif_exportable.
    ALIASES print FOR lif_printable~print.    " 별칭 설정

    METHODS constructor IMPORTING iv_order_id TYPE string
                                  iv_amount   TYPE p.
  PRIVATE SECTION.
    DATA: mv_order_id TYPE string,
          mv_amount   TYPE p LENGTH 13 DECIMALS 2.
ENDCLASS.

CLASS lcl_sales_order IMPLEMENTATION.
  METHOD constructor.
    mv_order_id = iv_order_id.
    mv_amount   = iv_amount.
  ENDMETHOD.

  METHOD lif_printable~print.
    rv_output = |주문번호: { mv_order_id } / 금액: { mv_amount }|.
  ENDMETHOD.

  METHOD lif_exportable~export_to_csv.
    rv_csv = |{ mv_order_id };{ mv_amount }|.
  ENDMETHOD.
ENDCLASS.

" 다형성 활용: 인터페이스 레퍼런스로 다양한 객체를 동일하게 처리
DATA lt_printables TYPE TABLE OF REF TO lif_printable.
DATA(lo_order1) = NEW lcl_sales_order( iv_order_id = 'SO-001' iv_amount = '15000.00' ).
DATA(lo_order2) = NEW lcl_sales_order( iv_order_id = 'SO-002' iv_amount = '28500.00' ).

APPEND lo_order1 TO lt_printables.
APPEND lo_order2 TO lt_printables.

LOOP AT lt_printables INTO DATA(lo_item).
  DATA(lv_output) = lo_item->print( ).
  WRITE: / lv_output.
ENDLOOP.

인터페이스 메서드 옵션

인터페이스를 구현하는 클래스에서 다음 옵션을 활용할 수 있습니다.

옵션 설명
ABSTRACT METHODS 지정한 메서드를 추상으로 선언 (서브클래스에서 구현)
ALL METHODS ABSTRACT 인터페이스의 모든 메서드를 추상으로 설정
FINAL METHODS 지정한 메서드를 재정의 불가로 잠금
DEFAULT IGNORE 구현하지 않아도 에러 없이 무시
DEFAULT FAIL 미구현 메서드 호출 시 런타임 에러 발생
상속 vs 인터페이스: 상속은 "A는 B의 한 종류다(IS-A)" 관계에, 인터페이스는 "A는 B를 할 수 있다(CAN-DO)" 관계에 적합합니다. 일반적으로 인터페이스를 먼저 고려하고, 코드 재사용이 필요할 때 상속을 추가하는 것이 권장됩니다.

8. 실무 활용 팁 - 설계 원칙과 실습 방법

설계 원칙

SE24 / ADT에서 실습하기

흔한 실수와 트러블슈팅

실수 원인 해결
super->constructor 미호출 서브클래스 constructor에서 슈퍼클래스 constructor를 빠뜨림 서브클래스 constructor의 첫 줄에서 반드시 super->constructor( ) 호출
CX_SY_MOVE_CAST_ERROR 덤프 다운캐스트 시 실제 타입과 불일치 IS INSTANCE OF 또는 TRY-CATCH로 사전 검사
정적 메서드 REDEFINITION 시도 정적 메서드는 재정의 불가 인스턴스 메서드로 변경하거나, 서브클래스에서 동명의 새 정적 메서드 선언
PRIVATE 속성 상속 기대 PRIVATE은 서브클래스에서 접근 불가 서브클래스 접근이 필요하면 PROTECTED로 변경
인터페이스 메서드명 틸데(~) 누락 lif~method 형식을 사용하지 않음 구현부에서 반드시 인터페이스명~메서드명 형식 사용, 또는 ALIASES 활용
FAQ:
  • Q: ABSTRACT 클래스와 INTERFACE의 차이는? - ABSTRACT 클래스는 일부 메서드를 구현할 수 있지만, INTERFACE는 구현을 포함하지 않습니다. ABSTRACT 클래스는 단일 상속만 가능하고, INTERFACE는 여러 개를 동시에 구현할 수 있습니다.
  • Q: class_constructor는 언제 실행되나요? - 해당 클래스가 세션 내에서 최초로 사용되는 시점(객체 생성, 정적 메서드 호출, 정적 속성 접근 등)에 자동으로 1회 실행됩니다.
  • Q: 메서드 체이닝은 어떻게 구현하나요? - RETURNING 파라미터의 타입을 자기 자신의 클래스 레퍼런스(REF TO)로 설정하고, 메서드 마지막에 result = me를 반환합니다.

다음 단계 / 관련 주제

ABAP OOP의 기초를 익혔다면, 다음 주제로 학습을 확장할 수 있습니다.

참고 자료