"In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed." (Wikipedia)
A very unused yet extremely simple software design technique is the lazy initialization (sometimes referred to as lazy instantiation or lazy loading). This is exceedingly helpful in large applications where a massive amount of data must be loaded and/or calculated at runtime.
For a better understanding, the following ABAP scenario is taken into consideration:
A company/client needs an application to display all the information related to a single entity model. Because the amount of that cannot fit into a single database table or a single screen, a decision has been taken to implement the display in the form of a tab strip, where each of the tabs will encapsulate information in different forms with the possibility to integrate different data sources (E.g. entry/entries from the underlying or external database, calculations, aggregates, RFC calls, Web Services, etc.).
In this example, several database tables will be used, each will correspond to one of the tabs:
The database objects will be uniquely identified by a key value structure, which will be referred to as the "common key". When the application is started and the tab strip is instantiated, the only known data is:
- The common key
- ID of the first displayed tab
(For clarity purposes, each data source will be named according to the corresponding tab: D1, D2 and so on.)
Because the business logic must be UI-independent, the application will be structured in two packages:
- Business object (BO) layer – encapsulates the data model and functionality
- User interface (UI) layer
Using a bottom-up approach, the BO layer will be designed first. For each data sources (in this case: ZD1, ZD2, etc.) a corresponding business class must be created.
ZCL_FACTORY will act as a bottleneck for the creation of the subordinate objects, which can only be instantiated by the factory method.
As the factory class contains a list of already instantiated BOs, it should also be a Singleton. The factory's constructor method is optional, but it can be used for example to either enqueue existing data in Change-mode or generated the common key in Create-mode.
A snippet of the ZCL_FACTORY class with some explanations:
CLASS zcl_factory DEFINITION
PUBLIC
FINAL
CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS get_instance
IMPORTING
value(iv_common_key) TYPE guid OPTIONAL
RETURNING
value(ro_factory) TYPE REF TO zcl_factory.
METHODS constructor
IMPORTING
value(iv_common_key) TYPE guid OPTIONAL.
METHODS factory
IMPORTING
value(iv_type) TYPE string
RETURNING
value(ro_bo) TYPE REF TO zcl_abstract_bo.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_list,
classname TYPE classname,
bo TYPE REF TO zcl_abstract_bo,
END OF ts_list,
tt_list TYPE STANDARD TABLE OF ts_list.
CLASS-DATA :
mo_factory TYPE REF TO zcl_factory.
DATA :
mt_list TYPE tt_list,
mv_common_key TYPE guid.
ENDCLASS.
CLASS zcl_factory IMPLEMENTATION.
METHOD constructor.
mv_common_key = iv_common_key.
ENDMETHOD.
METHOD factory.
DATA :
lv_classname TYPE classname.
FIELD-SYMBOLS :
<fs_list> TYPE ts_list.
* Obviously these hard-coded values are not optimal
CASE iv_type.
WHEN 'D1'
OR 'D2'
OR 'Dx'.
lv_classname = |ZCL_BO_{ iv_type }|.
WHEN OTHERS.
* RAISE EXCEPTION TYPE ...
ENDCASE.
* Check if the list already contains an instance
READ TABLE mt_list ASSIGNING <fs_list>
WITH KEY classname = lv_classname.
* If not, create
IF NOT sy-subrc IS INITIAL.
APPEND INITIAL LINE TO mt_list ASSIGNING <fs_list>.
<fs_list>-classname = lv_classname.
CREATE OBJECT <fs_list>-bo TYPE (lv_classname)
EXPORTING
iv_common_key = mv_common_key.
ENDIF.
* Return BO instance
ro_bo = <fs_list>-bo.
ENDMETHOD.
METHOD get_instance.
* Singleton method to retrieve factory instance
IF mo_factory IS INITIAL.
* Just as an example for Create/Display
IF iv_common_key IS SUPPLIED.
CREATE OBJECT mo_factory
EXPORTING
iv_common_key = iv_common_key.
ELSE.
TRY.
CREATE OBJECT mo_factory
EXPORTING
iv_common_key = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ).
CATCH cx_uuid_error.
* ...
ENDTRY.
ENDIF.
ENDIF.
ro_factory = mo_factory.
ENDMETHOD.
ENDCLASS.
Note: Attribute MT_LIST can also be used as a mechanism to control methods like: SAVE, CHECK_BEFORE_SAVE, HAS_CHANGES, and so on; but this depends on particular scenarios.
At this point, each specific BO can be implemented to match the particular use case.
- E.g. ZCL_BO_D1 reads a single entry from the database and displays it as a form. Therefore each field will have corresponding getter and setter methods. Alternatively, ZCL_BO_D2 will select a number of rows from the database and output them as a table. In which case, CRUD functionalities might be more adequate.
The constructor of each BO is responsible to load the data buffer using the given common key.
Note: As all the business objects have the property CREATE PRIVATE, each one must declare ZCL_FACTORY as friend.
The following statement can be used to create a BO instance for D1:
DATA:
lo_bo TYPE REF TOzcl_bo_d1.
lo_bo ?= zcl_factory=>get_instance( )->factory( 'D1' ).
After the business logic is active and secured, it is time to move up towards the UI layer. For this example, the user interface has been implemented both as an ABAP report and a Web Dynpro application. Nevertheless any other UI technology can be used.
1. ABAP Report
The most elementary implementation requires a main screen (100) and two sub screens (101, 102). The latter are embedded into a tab strip control belonging to the main screen. The trick here consists of adding the BO instantiation in the PBO module of each sub screen. As the PAI module of screen 100 controls the tab strip actions, the corresponding PBO modules are only called upon when necessary. This means that data is only loaded when displayed.
2. Web Dynpro Application
The application consists of a main WD component with a start view and an embedded TabStrip UI element. Based on the complexity of the application, it is a matter of choice how each tab is implemented:
2.1. Directly in the same view
2.2. As a different view inside the same application
2.3. As a separate WD component
Either way, the component controller (from the main component 2.1., 2.2., or the each used component, 2.3.) must implement initialization method(s) – for 2.3., these methods must be defined as interface.
The catch in this situation is that TabStrip UI element has an event called ON_SELECT. For this event, an associated event handler is implemented in the start view of the main component. Similar to the PAI module, this method is used to determine the navigation and therefore control the BO instantiation.
METHOD onactionon_select.
CASE wdevent->get_string( 'TAB' ).
WHEN 'D1'.
WHEN 'D2'.
* 2.1., 2.2.
wd_comp_controller->init_d2( ).
* OR
* 2.3.
wd_this->wd_cpifc_d2( )->init( ).
WHEN 'Dx'.
ENDCASE.
ENDMETHOD.
Note: This can also be implemented using Floorplan Manager (FPM) as an OIF component by checking the navigated tab in method PROCESS_BEFORE_OUTPUT.
Evidently, it is important to set the most used tab as initial or give the possibility for the user to choose.
In the end, this is the most rudimentary example I could think of, but it can nevertheless be scaled tremendously.
Thank you and please feel free to contribute. My only hope is that I didn't compress the content to the extent that it is not understandable.
Tudor