캡슐화를 사용하여 일관성 보장

Objectives

After completing this lesson, you will be able to:
  • 캡슐화 이해
  • 생성자를 정의하고 사용합니다.

데이터 캡슐화

객체 지향 프로그래밍에서 오브젝트는 사원, 차량 또는 항공편 연결과 같은 실제 오브젝트에 해당합니다. 여기에는 이를 설명하는 특성이 있으며, 항공편 연결의 경우 항공사 ID와 항공편 번호가 있습니다.

이제 이 오브젝트를 사용하는 프로그램에서 이러한 특성에 대한 값을 제공하려고 할 때 어떤 일이 발생하는지 살펴보겠습니다. 실제 세계의 항공편 연결에 해당하는 항공사 ID와 항공편 번호의 조합만 허용되어야 한다는 것은 분명합니다.

오브젝트 방향에서 클라이언트 프로그램은 특성 값을 직접 변경할 수 없어야 합니다. 대신 이 태스크를 수행하기 위해 메소드를 호출해야 합니다. 그런 다음 오브젝트와 함께 제공되는 메소드를 통해 항공사 ID와 항공편 번호의 조합이 유효한지 확인하고 그렇지 않은 경우 변경을 거부할 수 있습니다.

lcl_connection 클래스의 개발자로서, carrier_id 및 connection_id 전용 또는 최소한 읽기 전용 특성을 만들어 set_attributes( ) 메소드를 사용할 수 있습니다.

이 개념을 데이터 캡슐화라고 합니다. 항공편 연결에 대한 정보는 연결 오브젝트 자체에서 관리되며 다른 사람이 조작할 수 없습니다. 이렇게 하면 고의로 또는 모르게 일관성 점검을 바이패스할 수 없습니다. 이는 객체 지향의 주요 장점 중 하나입니다.

오브젝트 지향 프로그래밍에서는 최대한 데이터 캡슐화를 사용하는 것이 좋습니다!

이제 에서 정의한 공용 특성을 사용하여 클래스 내부 및 외부의 어디에서나 특성 값을 읽고 변경할 수 있었습니다.

특성에 대한 액세스를 제한하기 위해 다음 두 가지 옵션을 사용할 수 있습니다.

  1. DATA 문 또는 CLASS-DATA 문을 공용 섹션에 유지하고 추가 요소 READ-ONLY를 추가합니다. 이렇게 하면 클래스 외부에서 특성에 대한 쓰기 액세스가 금지되지만 읽기 액세스는 여전히 가능합니다.

    노트

    추가 요소 READ-ONLY는 클래스의 public 섹션에서만 허용됩니다.
  2. DATA 문 또는 CLASS-DATA 문을 공용 섹션에서 다른 가시성 섹션(예: private 섹션) 중 하나로 이동합니다. 이렇게 하면 클래스 외부에서 특성에 대한 읽기 액세스 및 쓰기 액세스가 금지됩니다.

    힌트

    ADT는 특성 표시 여부를 변경하기 위한 Quick Fix를 제공합니다. 이를 사용하려면 특성 이름에 커서를 놓고 Ctrl + 1 을 누른 다음 Make <attribute> private 을 선택합니다.

개인 특성 사용 방법

인스턴스 생성자

특성을 개인 또는 읽기 전용으로 설정하면 클라이언트 프로그램에서 사용 가능한 set_attributes( ) 메소드를 사용하여 특성 carrier_id 와 connection_id 의 값을 설정하도록 할 수 있습니다.

하지만 여전히 다음과 같은 불일치가 발생할 수 있습니다.

  • 새 인스턴스마다 set_attributes( ) 를 강제로 호출할 수 없습니다. 따라서 carrier_id 와 connection_id 에 대한 초기 값을 갖는 인스턴스가 있을 수 있습니다.
  • 클라이언트 프로그램은 동일한 인스턴스에 대해 set_attributes( )를 여러 번 호출할 수 있습니다. 이 역시 가능하지 않아야 합니다.

필요한 것은 인스턴스화 중에 초기 값이 아닌 값을 적용하고 나중에 변경하는 것을 방지하는 기술입니다.

이러한 문제를 해결하기 위해 객체 지향 프로그래밍 언어는 생성자 메소드를 사용합니다.

런타임 시스템은 클래스의 신규 인스턴스를 생성할 때 생성자를 자동으로 호출하지만 명시적으로 호출할 수는 없습니다. 따라서 생성자는 생성하는 각 인스턴스에 대해 한 번만 실행되도록 보장됩니다.

구문 관점에서 생성자 메소드의 속성은 다음과 같습니다.

  • 예약된 이름 생성자가 있는 공용 인스턴스 메소드
  • 예를 들어 신규 인스턴스의 특성에 대한 시작 값을 가져오기 위해 임포트 매개변수가 있을 수 있습니다.
  • 예외가 발생할 수 있음

노트

ABAP에서는 동일한 클래스에 여러 개의 생성자 메소드를 정의할 수 없습니다.

힌트

ADT에서는 Quick Fix를 사용하여 생성자 메소드를 생성할 수 있습니다. 이 Quick Fix를 사용하려면 다음과 같이 하십시오.

  1. 정의 또는 구현 부분에서 클래스 이름에 커서를 놓고 Ctrl + 1 을 누릅니다.
  2. 사용 가능한 Quick Fix 리스트에서 생성자 생성 을 선택합니다.
  3. 대화 상자 윈도우가 나타나면 생성자로 초기화할 특성을 선택하고 Finish 를 선택합니다.

생성자가 생성되면 필요에 따라 생성자의 정의와 구현을 조정할 수 있습니다.

이 예에서는 lcl_connection 클래스의 생성자를 보여 줍니다. 생성된 정의에는 각 특성 carrier_idconnection_id에 대한 임포트 매개변수가 포함되어 있습니다.

구현의 생성된 부분에는 각 특성에 대한 값 지정이 오른쪽에 관련 임포트 매개변수와 함께 포함되어 있습니다.

이 예에서는 lcl_connection 클래스의 생성된 생성자에 수동으로 추가되는 내용을 보여 줍니다.

초기 값을 사용한 인스턴스 생성을 거부하기 위해 구현에 일관성 점검이 추가되었고 생성자 정의에 RAISING 절이 추가되었습니다.

생성자는 lcl_connection 클래스의 신규 인스턴스마다 한 번만 실행되기 때문에 정적 특성 conn_counter를 증가시킬 수 있는 완벽한 위치는 생성자 구현입니다.

생성자가 있는 클래스를 인스턴스화하면 생성자 메소드가 자동으로 호출됩니다. 생성자에 임포트 매개변수가 있으면 일반 메소드에 매개변수를 전달할 때 NEW 식에 이를 정확하게 전달합니다.

노트

생성자는 임포트 매개변수만 가질 수 있다. 키워드 EXPORTING은 NEW 표현식에서 필요하거나 허용되지 않습니다.
생성자에서 예외가 발생하면 해당 인스턴스화를 TRY… CATCH...ENDTRY 블록 안에 넣어서 예외를 캐치합니다. 생성자에서 예외가 발생하면 컨트롤이 NEW 식이 포함된 프로그램으로 즉시 돌아갑니다. 이러한 경우 클래스의 신규 인스턴스가 생성되지 않습니다.

인스턴스 생성자 정의 방법

정적 생성자(Static Constructor)

인스턴스 생성자는 각 클래스 인스턴스가 생성될 때 한 번 호출되지만 전체 클래스에 대해 한 번만 액션을 수행해야 하는 경우도 있습니다. 이를 위해 ABAP에서는 정적 생성자(클래스 생성자)를 정의할 수 있습니다.

런타임 시스템은 프로그램 실행 중에 클래스가 처음 처리될 때만 정적 생성자를 한 번 호출합니다.

클래스의 첫 번째 주소 지정은 다음 중 하나일 수 있습니다.

  • 클래스의 첫 번째 인스턴스화
  • 정적 메소드의 첫 번째 호출
  • 공용 정적 특성에 대한 첫 번째 액세스

노트

이 리스트가 완전하지 않습니다. 다른 액션(상속과 관련됨)으로도 클래스가 처음으로 처리됩니다.

정적 생성자의 일반적인 사용 케이스는 초기 값이 아닌 정적 특성의 동적 초기화입니다. 따라서 런타임에 인스턴스를 생성하거나, 정적 메소드를 호출하거나, 정적 특성을 처리하기 전에 정적 생성자를 호출하는 것이 중요합니다.

구문 관점에서 생성자 메소드의 속성은 다음과 같습니다.

  • 예약된 이름 class_constructor를 사용하는 공용 정적 메소드
  • 매개변수 또는 예외 없음

노트

정적 생성자에는 서명이 있어서는 안 됩니다. 클래스를 언제 처음으로 처리할지 정확히 알 수 없기 때문입니다.

힌트

ADT에서는 Quick Fix를 사용하여 클래스 생성자 메소드를 생성할 수 있습니다. 이 Quick Fix를 사용하려면 다음과 같이 하십시오.

  1. 정의 또는 구현 부분에서 클래스 이름에 커서를 놓고 Ctrl + 1 을 누릅니다.
  2. 사용 가능한 Quick Fix 리스트에서 Generate class constructor 선택

생성자 실행을 디버그하는 방법

개인 특성 및 생성자 사용

이 연습문제에서는 클래스의 정적 특성을 읽기 전용으로 설정하고 인스턴스 특성을 비공개로 설정합니다. 생성자를 사용하여 값을 설정합니다.

템플릿:

  • /LRN/CL_S4D400_CLS_METHODS(전역 클래스)

솔루션:

  • /LRN/CL_S4D400_CLS_CONSTRUCTOR(전역 클래스)

태스크 1: 템플릿 복사(선택사항)

템플릿 클래스를 복사합니다. 이전 연습문제를 완료한 경우 이 태스크를 건너뛰고 클래스(ZCL_##_METHODS, ZCL_##_LOCAL_CLASS 또는 ZCL_##_INSTANCES)를 계속 편집할 수 있습니다.

단계

  1. 클래스 /LRN/CL_S4D400_CLS_METHODS 를 자체 패키지의 클래스에 복사합니다(제안 이름: ZCL_##_CONSTRUCTOR, ##은 그룹 번호).

    1. 전역 클래스 /LRN/CL_S4D400_CLS_METHODS의 소스 코드를 엽니다.

    2. 프로젝트 탐색기 뷰를 편집기와 연결합니다.

    3. 프로젝트 탐색기 뷰에서 /LRN/CL_S4D400_CLS_METHODS 클래스를 마우스 오른쪽 버튼으로 클릭하여 단축 메뉴를 엽니다.

    4. 단축 메뉴에서 Duplicate .... 를 선택합니다.

    5. 패키지 필드에 패키지 이름을 입력합니다. 이름 필드에 이름 ZCL_##_CONSTRUCTOR 를 입력합니다. 여기서 ##은 그룹 번호입니다.

    6. 내역을 조정하고 다음 을 선택합니다.

    7. 전송 요청을 확인하고 Finish를 선택합니다.

태스크 2: 특성 개인 설정

set_attributes( )get_output( ) 메소드를 사용하도록 특성 carrier_idconnection_id 의 표시 여부를 변경합니다.

단계

  1. Quick Fix를 사용하여 특성 carrier_id의 표시 여부를 private으로 설정합니다.

    1. 로컬 유형 탭으로 전환합니다.

    2. 로컬 클래스 lcl_connectioncarrier_id 특성의 선언으로 이동합니다.

    3. carrier_id 에 커서를 놓고 Ctrl + 1 을 누릅니다.

    4. 제안인 carrier_id를 비공개로 설정 을 더블 클릭합니다.

    5. carrier_id 특성의 선언이 private 섹션으로 이동했는지 확인합니다.

  2. Quick Fix를 사용하여 connection_id 특성의 표시 여부를 private 으로 설정합니다.

    1. 로컬 클래스 lcl_connectionconnection_id 특성의 선언으로 이동합니다.

    2. connection_id 에 커서를 놓고 Ctrl + 1 을 누릅니다.

    3. 제안 make connection_id private 을 더블 클릭합니다.

    4. connection_id 특성의 선언이 private 섹션으로 이동했는지 확인합니다.

태스크 3: 인스턴스 생성자 생성

public 메소드 set_attributes를 인스턴스 생성자로 대체하여 신규 인스턴스를 생성하는 동안 특성이 설정되고 나중에 변경되지 않도록 합니다.

단계

  1. set_attributes 메소드의 정의와 구현을 주석 처리합니다.

    1. 기간 기호(.)가 있는 라인을 포함하여 METHODS set_attributes 문에 속하는 모든 라인을 선택합니다.

    2. Ctrl + < 를 눌러 선택한 각 라인 앞에 별표 기호(*)를 추가합니다.

    3. 해당 ENDMETHOD. 문을 포함하여 set_attributes 메소드 구현에 속하는 라인을 모두 선택하고 Ctrl + < 를 다시 누릅니다.

  2. Quick Fix를 사용하여 로컬 클래스 lcl_connection 에 인스턴스 생성자를 추가합니다. 생성자에 특성 carrier_idconnection_id 에 해당하는 임포트 매개변수가 있는지 확인합니다.

    1. 클래스 이름에 커서를 놓고 Ctrl + 1 을 누릅니다.

    2. 생성자 생성 을 더블 클릭합니다.

    3. 대화 상자에서 carrier_idconnection_id 가 선택되었는지 확인하고 Finish 를 선택합니다.

  3. 생성된 생성자 정의를 확장합니다. 메소드 생성자의 정의에 CX_ABAP_INVALID_VALUE 예외를 추가합니다.

    1. 생성된 메소드 생성자 정의로 이동합니다.

    2. 코드를 다음과 같이 조정합니다.

      Code Snippet
      12345678
      METHODS constructor IMPORTING i_connection_id TYPE /dmo/connection_id i_carrier_id TYPE /dmo/carrier_id RAISING cx_ABAP_INVALID_VALUE.
  4. 생성된 생성자 구현을 확장합니다. 임포트 매개변수 중 하나가 초기 값이면 예외 CX_ABAP_INVALID_VALUE를 발생시킵니다.

    1. 정의에서 메소드 생성자 구현으로 이동합니다(예: 생성자에 커서를 놓고 F3 누르기).

    2. 코드를 다음과 같이 조정합니다.

      Code Snippet
      123456789101112
      METHOD constructor. IF i_carrier_id IS INITIAL OR i_connection_id IS INITIAL. RAISE EXCEPTION TYPE cx_abap_invalid_value. ENDIF. me->connection_id = i_connection_id. me->carrier_id = i_carrier_id. ENDMETHOD.
  5. 생성자 구현에서 문을 추가하여 정적 특성 conn_counter 의 값을 하나씩 늘립니다. 예외가 발생하지 않은 경우에만 명령문이 실행되어야 합니다.

    1. 코드를 다음과 같이 조정합니다.

      Code Snippet
      12345678
      me->connection_id = i_connection_id. me->carrier_id = i_carrier_id. conn_counter = conn_counter + 1. ENDMETHOD.
  6. 클래스 외부에서 conn_counter 의 값을 변경할 수 없는지 확인하십시오.

    1. 정적 특성 conn_counter 의 선언에 READ-ONLY 를 추가합니다.

    2. 코드를 다음과 같이 조정합니다.

      Code Snippet
      123
      CLASS-DATA conn_counter TYPE i READ-ONLY.

태스크 4: 생성자 사용

lcl_connection 클래스의 인스턴스화를 조정하여 인스턴스 생성자의 매개변수를 제공하고 예외를 처리합니다.

단계

  1. if_oo_adt_classrun~main 메소드에서 lcl_connection의 첫 번째 인스턴스에 대한 NEW #( ) 표현식으로 이동합니다.

    1. 전역 클래스 탭으로 전환합니다.

    2. connection = NEW #( ). 을 사용하여 첫 번째 라인을 찾습니다.

  2. NEW 표현식에서 생성자의 임포트 매개변수를 제공합니다.

    힌트

    이 인스턴스의 set_attributes 메소드 호출에서 전달하는 매개변수를 복사할 수 있습니다.
    1. NEW 표현식을 다음과 같이 조정합니다.

      Code Snippet
      123456
      connection = NEW #( i_carrier_id = 'LH' i_connection_id = '0400' ).
  3. set_attributes 메소드의 모든 호출을 제거하거나 주석 처리합니다.

    1. 닫는 괄호와 마침표(.)가 있는 라인을 포함하여 connection->set_attributes( ... ). 문에 속하는 모든 라인을 선택합니다.

    2. Ctrl + < 를 눌러 선택한 각 라인 앞에 별표 기호(*)를 추가합니다.

  4. 인스턴스 생성을 예외 처리의 TRY 블록으로 이동합니다.

    1. TRY. 문을 인스턴스 생성 전으로 위로 이동합니다.

    2. 첫 번째 인스턴스 생성은 다음과 같아야 합니다.

      Code Snippet
      12345678910111213141516171819
      TRY. connection = NEW #( i_carrier_id = 'LH' i_connection_id = '0400' ). * connection->set_attributes( * EXPORTING * i_carrier_id = 'LH' * i_connection_id = '0400' * ). APPEND connection TO connections. CATCH cx_abap_invalid_value. out->write( `Method call failed` ). ENDTRY.
  5. 로컬 클래스의 다른 인스턴스에 대해 이전 단계를 반복합니다.

    1. 이 단계가 끝나면 if_oo_adt_classrun~main 메소드의 구현이 다음과 유사하게 표시되어야 합니다.

      Code Snippet
      1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
      METHOD if_oo_adt_classrun~main. DATA connection TYPE REF TO lcl_connection. DATA connections TYPE TABLE OF REF TO lcl_connection. * First Instance ********************************************************************** TRY. connection = NEW #( i_carrier_id = 'LH' i_connection_id = '0400' ). * connection->set_attributes( * EXPORTING * i_carrier_id = 'LH' * i_connection_id = '0400' * ). APPEND connection TO connections. CATCH cx_abap_invalid_value. out->write( `Method call failed` ). ENDTRY. * Second instance ********************************************************************** TRY. connection = NEW #( i_carrier_id = 'AA' i_connection_id = '0017' ). APPEND connection TO connections. CATCH cx_abap_invalid_value. out->write( `Method call failed` ). ENDTRY. * Third instance ********************************************************************** TRY. connection = NEW #( i_carrier_id = 'SQ' i_connection_id = '0001' ). APPEND connection TO connections. CATCH cx_abap_invalid_value. out->write( `Method call failed` ). ENDTRY. * Output ********************************************************************** LOOP AT connections INTO connection. out->write( connection->get_output( ) ). ENDLOOP. ENDMETHOD.
  6. 클래스를 활성화합니다. 이를 실행하고 인스턴스화를 디버그합니다.

    1. Ctrl + F3 을 눌러 클래스를 활성화합니다.

    2. F9 를 눌러 클래스를 실행합니다.