ABAP ООП Ч.3. Инкапсуляция.

ABAP ООП

 

Инкапсуляция

Инкапсуляция в ООП позволяет определить границы и скрыть реализацию от внешнего мира. Термин инкапсуляция означает, что атрибуты (данные) и методы (функции), которые манипулируют данными, заключены в капсулу (объект). Это означает, что мы можем установить границу между тем, к чему можно получить доступ изнутри объекта и из внешнего мира. Эта граница помогает решить многие проблемы, в отношении процедур.

Инкапсуляция

Допустим у нас есть принтер:
CLASS lcl_printer DEFINITION.

  PUBLIC SECTION.
    DATA mv_counter TYPE i. "Количество распечатанных страниц
    METHODS constructor.
    METHODS print.
    METHODS info.
ENDCLASS.

CLASS lcl_printer IMPLEMENTATION.
  METHOD constructor.
    mv_counter = 0. "Начальное значение
  ENDMETHOD.
  METHOD print.
    mv_counter = mv_counter + 1. "Инкремент
    WRITE 'Документ напечатан'.
  ENDMETHOD.
  METHOD info.
    WRITE: 'Количество распечатанных страниц',mv_counter.
  ENDMETHOD.
ENDCLASS.
   
Атрибутом принтера является "Количество распечатанных страниц" При создание объекта этого класса количество будет равно 0.
START-OF-SELECTION.
DATA lo_object TYPE REF TO lcl_printer.
CREATE OBJECT lo_object.
lo_object->print( ).
lo_object->info( ).
   
Все работает так как задумано.



Однако в этой реализации есть одна лазейка: поскольку атрибут определен с его видимостью как общедоступный, любой может получить прямой доступ этот атрибут извне класса и установить значение, тем самым отображения информация будет неверна.
START-OF-SELECTION.
DATA lo_object TYPE REF TO lcl_printer.
CREATE OBJECT lo_object.
lo_object->print( ).
lo_object->mv_counter = 0. "Установили начальное значение
lo_object->info( ).
   

Результат:

Документ был распечатан, а информации об этом нет.

Именно здесь инкапсуляция и сокрытие реализации играют важную роль. Определяя четкие границы того, к чему можно получить доступ извне класса, а что должно быть ограничено, мы не только облегчаем разработчикам использование наших классов, поскольку им нужно знать только общедоступный интерфейс (общедоступные компоненты) нашего класса. class, но и гарантировать, что непреднамеренные ошибки не проникнут в нашу программную реализацию.
Инкапсуляция также дает вам свободу изменять приватные разделы класса в соответствии с изменяющимися требованиями, не беспокоясь о зависимостях. Например, если вы измените интерфейс метода в разделе общедоступной видимости, это повлияет на все программы, вызывающие этот метод; однако, поскольку ни одна программа не может получить прямой доступ к приватной секции класса, вы можете свободно изменять приватные секции, не беспокоясь о программных зависимостях.

В этом примере мы можем сделать атрибут закрытым.
CLASS lcl_printer DEFINITION.

  PUBLIC SECTION.
    METHODS constructor.
    METHODS print.
    METHODS info.
  PRIVATE SECTION.
    DATA mv_counter TYPE i. "Количество распечатанных страниц
ENDCLASS.

CLASS lcl_printer IMPLEMENTATION.
  METHOD constructor.
    mv_counter = 0. "Начальное значение
  ENDMETHOD.
  METHOD print.
    mv_counter = mv_counter + 1. "Инкремент
    WRITE: / 'Документ напечатан'.
  ENDMETHOD.
  METHOD info.
    WRITE: / |Количество распечатанных страниц = { mv_counter }|.
  ENDMETHOD.
ENDCLASS.   
Как видите, мы усложняли наш принтер, не дублируя код. Кроме того, поскольку к принтеру добавились дополнительные возможности, он по-прежнему выполнял основную задачу печати документа. Следовательно, пользователю, использующему усовершенствованную модель принтера, не нужно знать о его дополнительных функциях для выполнения основной задачи печати документа. Это похоже на автомобиль с расширенными функциями, такими как круиз-контроль. Водителю не обязательно знать, как настроить круиз-контроль, чтобы управлять автомобилем, но он может научиться настраивать круиз-контроль, чтобы пользоваться им. 
START-OF-SELECTION.
  DATA lo_object TYPE REF TO lcl_printer.
  CREATE OBJECT lo_object.
  lo_object->print( ).
  lo_object->mv_counter = 0.
  lo_object->info( ).
В этом примере мы уже получим ошибку:

Видимость компонента

Как показано к компоненту объекта можно получить доступ из внешней программы, из подкласса или из класса. Ограничение доступа к компоненту класса определяет его видимость. Другими словами, мы можем решить, можно ли получить доступ к компоненту класса только из самого класса или его подклассов или из внешних программ.


Видимость компонента
Область видимости Доступ компонентами этого же класс Доступ компонентами наследника Доступ внешними объектами
Public YES YES YES
Protected YES YES NO
Private YES NO NO
Переменные в методе NO NO NO

Мы  уже определили разделы в которых объявлены компоненты. Эти разделы называются разделами видимости. Есть три раздела видимости: общедоступный, защищенный и частный. Раздел видимости определяет, как можно получить доступ к компоненту класса. В следующем списке определены различные разделы видимости:

Public section - Публичный раздел
Доступ к компонентам в общедоступном разделе можно получить как внутри класса, так и за его пределами. Эти компоненты образуют общедоступный интерфейс класса.

Protected section - Защищенный раздел
Доступ к компонентам защищенного раздела возможен только из класса и его подклассов (дочерних классов). Доступ к этим компонентам из внешних программ недоступен.

Private section - Приватный раздел
Доступ к компонентам в приватной секции возможен только из класса и его дружественных классов.
CLASS lcl_printer DEFINITION.

CLASS lcl_encapsulation_demo DEFINITION.
  PUBLIC SECTION.
    METHODS print.
    METHODS set_copies IMPORTING iv_copies TYPE i.
    METHODS get_counter EXPORTING ev_counter TYPE i.
  PROTECTED SECTION.
    METHODS reset_counter.
  PRIVATE SECTION.
    DATA mv_copies TYPE i.
    DATA mv_counter TYPE i.
    METHODS reset_copies.
ENDCLASS.
CLASS lcl_encapsulation_demo IMPLEMENTATION.
  METHOD print.
    "Бизнес-логика
  ENDMETHOD.
  METHOD set_copies.
    "Бизнес-логика
    me->mv_copies = iv_copies.
  ENDMETHOD.
  METHOD get_counter.
    "Бизнес-логика
    ev_counter = me->mv_counter.
  ENDMETHOD.
  METHOD reset_counter.
    "Бизнес-логика
    CLEAR mv_counter.
  ENDMETHOD.
  METHOD reset_copies.
    "Бизнес-логика
    CLEAR mv_copies.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_encapsulation_sub_demo DEFINITION INHERITING FROM lcl_encapsulation_demo.
  PROTECTED SECTION.
    METHODS reset_counter REDEFINITION.
ENDCLASS.
CLASS lcl_encapsulation_sub_demo IMPLEMENTATION.
  METHOD reset_counter.
    super->reset_counter( ).
* super->reset_copies( ). "выдает синтаксическую ошибку
  ENDMETHOD.
ENDCLASS.
   
В реализует класс lcl_encapsulation_demo, а класс lcl_encapsulation_sub_demo определен как подкласс lcl_encapsulation_demo. В коде показан синтаксис для реализации разделов видимости. Обратите внимание на порядок реализации разделов видимости: сначала определяются общедоступные компоненты, затем защищенные компоненты и, наконец, частные компоненты. Этот порядок объявления не может быть изменен.

В подкласс lcl_encapsulation_sub_demo может получить доступ только к общедоступным и защищенным компонентам своего суперкласса lcl_encapsulation_demo. Если класс lcl_encapsulation_sub_demo попытается получить доступ к закрытым компонентам своего суперкласса, эта попытка приведет к синтаксической ошибке. Обратите внимание, что при переопределении метода суперкласса в подклассе нельзя изменить раздел видимости метода. Например, если метод определен в защищенном разделе суперкласса, то метод должен быть переопределен в защищенном разделе подкласса.

С помощью секций видимости можно определить четкие границы, чтобы внешнему миру были доступны только необходимые компоненты объекта. Такое расположение обеспечивает большую гибкость при проектировании объектов, поскольку частные и защищенные компоненты класса могут быть изменены в соответствии с меняющимися бизнес-требованиями, не затрагивая внешние программы, которым нужно беспокоиться только об общедоступном интерфейсе.

Сохраняя атрибуты объекта закрытыми и реализуя методы установки и получения в классе, мы получаем больше контроля и безопасности над данными объекта; любое изменение атрибутов следует запрашивать с помощью методов установки, а любой запрос информации следует запрашивать с помощью методов получения. Такое расположение обеспечивает лучшую абстракцию и предотвращает нежелательные сценарии повреждения данных, что, в свою очередь, помогает объектам работать предсказуемым образом.

Например, реализуя метод установки для установки значения атрибута, мы можем гарантировать, что вся бизнес-логика будет применена до того, как значение будет изменено. Такой уровень уверенности невозможен, если внешняя программа напрямую обращается к атрибуту для присвоения значения, над которым мы не имеем никакого контроля.

Friends - Друзья

Класс может объявить другой класс своим другом, чтобы разрешить доступ ко всем своим компонентам, включая его частные и защищенные компоненты. Дружественный класс — это любой класс, который явно определен как друг в определении класса. Отношения дружбы не взаимны; например, если класс lcl_1 объявляет класс lcl_2 как друга, то методы класса c2 могут получить доступ к закрытым компонентам класса lcl_1, но методы класса lcl_1 не могут получить доступ к закрытым компонентам класса lcl_2.
  CLASS lcl_1 DEFINITION DEFERRED.
  CLASS lcl_2 DEFINITION DEFERRED.

CLASS lcl_encapsulation_demo DEFINITION FRIENDS lcl_1 lcl_2.
  PUBLIC SECTION.
    METHODS print.
    METHODS set_copies IMPORTING iv_copies TYPE i.
    METHODS get_counter EXPORTING ev_counter TYPE i.
  PROTECTED SECTION.
    METHODS reset_counter.
  PRIVATE SECTION.
    DATA mv_copies TYPE i.
    DATA mv_counter TYPE i.
    METHODS reset_copies.
ENDCLASS.
CLASS lcl_encapsulation_demo IMPLEMENTATION.
  METHOD print.
    "Бизнес-логика
  ENDMETHOD.
  METHOD set_copies.
    "Бизнес-логика
    me->mv_copies = iv_copies.
  ENDMETHOD.
  METHOD get_counter.
    "Бизнес-логика
    ev_counter = me->mv_counter.
  ENDMETHOD.
  METHOD reset_counter.
    "Бизнес-логика
    CLEAR mv_counter.
  ENDMETHOD.
  METHOD reset_copies.
    "Бизнес-логика
    CLEAR mv_copies.
  ENDMETHOD.
ENDCLASS.



CLASS lcl_1 DEFINITION.
  PUBLIC SECTION.
    METHODS get_counter IMPORTING iv_counter TYPE i.
  PROTECTED SECTION.
    DATA mv_counter TYPE i.
ENDCLASS.
CLASS lcl_1 IMPLEMENTATION.
  METHOD get_counter.
    DATA oref TYPE REF TO lcl_encapsulation_demo.
    CREATE OBJECT oref.
    oref->reset_counter( ).
    oref->mv_counter = iv_counter.
  ENDMETHOD.
ENDCLASS.
CLASS lcl_2 DEFINITION.
  PUBLIC SECTION.
    METHODS set_copies IMPORTING iv_copies TYPE i.
  PRIVATE SECTION.
    DATA mv_copies TYPE i.
ENDCLASS.
CLASS lcl_2 IMPLEMENTATION.
  METHOD set_copies.
    DATA oref TYPE REF TO lcl_encapsulation_demo.
    CREATE OBJECT oref.
    oref->reset_copies( ).
    oref->mv_copies = iv_copies.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_encapsulation_sub_demo DEFINITION INHERITING FROM lcl_encapsulation_demo.
  PROTECTED SECTION.
    METHODS reset_counter REDEFINITION.
ENDCLASS.
CLASS lcl_encapsulation_sub_demo IMPLEMENTATION.
  METHOD reset_counter.
    super->reset_counter( ).
* super->reset_copies( ). "дает syntax error
  ENDMETHOD.
ENDCLASS.
   
В реализует класс lcl_encapsulation_demo и определяет классы lcl_1 и lcl_2 как дружественные с помощью добавления FRIENDS в операторе CLASS DEFINITION. Любое количество классов может быть указано с добавлением FRIENDS, разделенным пробелами. Также обратите внимание на операторы CLASS lcl_1 DEFINITION DEFERRED и CLASS lcl_2 DEFINITION DEFERRED в начале кода. Эти два оператора сообщают средству проверки синтаксиса, что классы lcl_1 и lcl_2 определены позже в коде; в противном случае средство проверки синтаксиса выдаст ошибку, когда класс lcl_encapsulation_demo ссылается на классы lcl_1 и lcl_2 как на друзей в своем определении.

Классы lcl_1 и lcl_2 могут получить доступ к открытым, закрытым и защищенным компонентам lcl_encapsulation_demo, поскольку они определены как друзья для этого класса. Однако класс lcl_encapsulation_demo не может получить доступ к защищенным или закрытым разделам классов lcl_1 или lcl_2, поскольку отношения не являются взаимными. Без этого ограничения любой класс может объявить себя другом другого класса для доступа к его закрытым или защищенным компонентам, что противоречит цели наличия разделов видимости.

Скрытие реализации

Сокрытие реализации от внешнего мира позволяет лучше контролировать реализацию, обеспечивая при этом гибкость для будущих улучшений в связи с постоянно меняющимися бизнес-требованиями. Сведение общедоступного интерфейса к минимуму позволяет изменять частные и защищенные разделы класса в соответствии с изменяющимися требованиями, не беспокоясь о существующих приложениях, вызывающих класс. Это избавляет от головной боли во время усовершенствований и обслуживания.

Давайте рассмотрим вариант использования, чтобы понять, насколько полезно скрытие реализации. В этом примере предположим, что вы разрабатываете приложение для мобильной операционной системы, такой как iOS или Android. Одной из функций этого программного обеспечения является отображение пользователю уведомлений из разных приложений. Чтобы облегчить это, определите общедоступный атрибут в классе интерфейса прикладного программирования (API), к которому разработчики приложений смогут обращаться для отправки уведомлений. Однако таким образом вы не можете контролировать количество уведомлений, отображаемых сторонним приложением, или контролировать любую спам-активность. В показан пример кода API.

Класс lcl_notification_api определен с помощью атрибута mv_message и методов set_message и display_notification. Предположим, что lcl_notification_api работает как API, который можно вызывать из сторонних приложений для отображения уведомлений пользователю.

Атрибут  mv_message содержит сообщение, которое можно установить, вызвав метод set_message. Уведомление отображается путем вызова метода display_notification. В этом классе все компоненты общедоступны.

Поскольку все компоненты видны за пределами класса, любое их изменение нарушит реализацию во всех приложениях, использующих этот API. Например, реализация дополнительных функций, таких как фильтрация сообщений или ограничение количества сообщений в приложении, может быть затруднена при таком дизайне.

Метод установки set_message предназначен для установки сообщения после его проверки (например, проверки на наличие неподходящих слов). Однако это ограничение может быть легко обойдено сторонними разработчиками приложений, поскольку атрибут  mv_message также доступен извне класса. Поэтому сторонние разработчики могут напрямую обращаться к атрибуту для установки сообщения вместо вызова метода set_message.
CLASS lcl_notification_api DEFINITION.
  PUBLIC SECTION.
    METHODS set_message IMPORTING iv_message TYPE string.
    METHODS display_notification.
  PRIVATE SECTION.
    DATA mv_message TYPE string.
    METHODS filter_message RETURNING VALUE(rv_boolean) TYPE boolean.
    METHODS check_count RETURNING VALUE(rv_boolean) TYPE boolean.
    METHODS check_status RETURNING VALUE(rv_boolean) TYPE boolean.
ENDCLASS.
CLASS lcl_notification_api IMPLEMENTATION.
  METHOD set_message.
    mv_message = iv_message.
  ENDMETHOD.
  METHOD display_notification.
    IF me->filter_message( ) EQ abap_true OR
    me->check_count( ) EQ abap_true OR
    me->check_status( ) EQ abap_true.
      WRITE mv_message.
    ELSE.
      CLEAR mv_message.
    ENDIF.
  ENDMETHOD.
  METHOD filter_message.
*Здесь находится логика фильтрации, а параметр "Boolean" установлен в
*abap_true или abap_false соответственно.
  ENDMETHOD.
  METHOD check_count.
*Здесь идет логика проверки количества сообщений, а для параметра 
*"Boolean" устанавливается значение abap_true abap_false соответственно.
  ENDMETHOD.
  METHOD check_status.
* Логика проверки пользовательской персональной настройки здесь и параметр
*"Boolean" устанавливается в abap_true или abap_fals соответственно.
  ENDMETHOD.
ENDCLASS.


*Код в вызывающей программе.
DATA notify TYPE REF TO lcl_notification_api.
CREATE OBJECT notify.
notify->set_message( iv_message = 'Уведомление о моем приложении').
notify->display_notification( ).
Теперь перемещает атрибут сообщения в приватный раздел, чтобы к нему нельзя было получить доступ за пределами класса, а это означает, что его реализация скрыта от внешнего мира. Кроме того, в приватном разделе определены три новых метода для выполнения различных проверок сообщения перед его отображением пользователю; например, если пользователь отключил уведомления для приложения, программа может проверить эту информацию в методе check_status, и сообщение не отображается, если метод check_status возвращает логическое значение abap_false. Точно так же, если пользователь установил ограничение на отображение не более пяти уведомлений в день для приложения, метод check_count может обработать эту проверку. Методы проверки, используемые в приватном разделе, можно улучшать или изменять, не беспокоясь о каких-либо зависимостях.

Внешним разработчикам не нужно знать о тонкостях проверок, выполняемых для отображения их уведомлений. Все, что им нужно знать, это как установить сообщение и вызвать API уведомлений. С реализацией у вас есть полный контроль над отображаемыми уведомлениями, и нет возможности обойти какие-либо проверки. Это также делает реализацию ориентированной на будущее, поскольку вы можете добавить столько дополнительных проверок, сколько захотите, без каких-либо изменений внешних разработчиков в способе вызова API в своих приложениях.

При правильном использовании скрытие реализации делает разработку программного обеспечения менее подверженной ошибкам и упрощает ее усовершенствование или сопровождение.

Теперь, когда мы рассмотрели основные характеристики инкапсуляции, в следующем разделе мы перейдем к другой концепции ООП: наследованию.

Перевод книги #Complete ABAP / Kiran Bandari

Комментарии