When doing complex coding, there’s always the dilemma of using or non using global variables. We all know that global variables are dangerous because you never know where they are set and what value they carry in a specific method.
On the other hand, if I keep everything local, I am ending with huge lists of parameters in every method just to pass the values needed to the deepest nested method where I need it.
An example out of my daily work: I am refactoring a huge print program where a lot of global variables are set in various locations of the code. Let’s take one simple variable, which is the plant code. It is used in various methods because many utility classes I use take it as parameter. The plant is determined when the order that is to be printed is being read from db. If I make it local, I have to pass it to dozens of methods that need to know about the plant. On the other hand, if I make an instance attribute of it, I never can be sure that it is set when using it somewhere around in the code.
What I have startet to use for this case is a class I call data provider that keeps all data to be re-used throughout the code. It is instantiated at the very start of processing and keeps only data that is set once and remains set to its first value during the whole process. All attributes of that class are populated with instantiation and can not be changed any more (there are only getter methods for the values). In my main class, I instantiate the data provider as soon as possible and store the reference in an instance attribute so it is available to all methods.
Example
In this example report, a pp order is given and all occurring work centers in the operations are being determined and sent to output. In this simple program the usage of a data provider may be too much overhead, but if the program grows and you need to access the order data in many nested methods, it is as handy as global data but without the danger of overwriting it accidentally. The data provider gives also transparency about where the data comes from. Just use the where-used list and you understand that the data is provided in the constructor.
I would appreciate to know about your approaches!
*&---------------------------------------------------------------------*
*& Report ZP_TMP_DATA_PROVIDER
*&
*&---------------------------------------------------------------------*
*& Demo for SCN blog post "The global variable dilemma"
*& This report creates a list of all work center header data
*& used in a production order
*&---------------------------------------------------------------------*
report zp_tmp_data_provider.
parameters p_ordid type aufnr.
class lcl_data_provider definition.
public section.
types gty_t_operations type standard table of afvc with default key.
methods constructor importing iv_orderid type aufnr.
methods get_header returning value(rs_res) type aufk.
methods get_pp returning value(rs_res) type afko.
methods get_operations returning value(rt_res) type gty_t_operations.
private section.
data: ms_order_header type aufk,
ms_order_pp type afko,
mt_operations type gty_t_operations.
endclass.
class lcl_data_provider implementation.
method constructor.
" general order data
select single * from aufk
where aufnr = @iv_orderid
into @ms_order_header.
" production-related order data
select single * from afko
where aufnr = @iv_orderid
into @ms_order_pp.
" order operations
select * from afvc
where aufpl = @ms_order_pp-aufpl
into table @mt_operations.
endmethod.
" getter methods for read-only access to the global data
method get_header.
rs_res = ms_order_header.
endmethod.
method get_pp.
rs_res = ms_order_pp.
endmethod.
method get_operations.
rt_res = mt_operations.
endmethod.
endclass.
class lcl_app definition.
public section.
types: gty_t_crhd type standard table of crhd with default key.
methods constructor importing iv_orderid type aufnr.
methods get_workcenter_list returning value(rt_res) type gty_t_crhd.
private section.
data: mo_data_provider type ref to lcl_data_provider.
methods get_workcenter_oper
importing is_oper type afvc
returning value(rs_res) type crhd.
endclass.
class lcl_app implementation.
method constructor.
" instantiate the data provider
mo_data_provider = new #( iv_orderid ).
endmethod.
method get_workcenter_list.
" use data provider for accessing global data without being able
" to modify it
loop at mo_data_provider->get_operations( ) into data(ls_oper).
data(ls_workcenter) = get_workcenter_oper( ls_oper ).
collect ls_workcenter into rt_res.
endloop.
endmethod.
method get_workcenter_oper.
call function 'CR_WORKSTATION_READ'
exporting
id = is_oper-arbid " Work center ID
msgty = 'E' " Message type
importing
ecrhd = rs_res. " Header data
endmethod.
endclass.
start-of-selection.
cl_demo_output=>display(
new lcl_app( p_ordid )->get_workcenter_list( ) ).
On the other hand, if I keep everything local, I am ending with huge lists of parameters in every method just to pass the values needed to the deepest nested method where I need it.
An example out of my daily work: I am refactoring a huge print program where a lot of global variables are set in various locations of the code. Let’s take one simple variable, which is the plant code. It is used in various methods because many utility classes I use take it as parameter. The plant is determined when the order that is to be printed is being read from db. If I make it local, I have to pass it to dozens of methods that need to know about the plant. On the other hand, if I make an instance attribute of it, I never can be sure that it is set when using it somewhere around in the code.
What I have startet to use for this case is a class I call data provider that keeps all data to be re-used throughout the code. It is instantiated at the very start of processing and keeps only data that is set once and remains set to its first value during the whole process. All attributes of that class are populated with instantiation and can not be changed any more (there are only getter methods for the values). In my main class, I instantiate the data provider as soon as possible and store the reference in an instance attribute so it is available to all methods.
Example
In this example report, a pp order is given and all occurring work centers in the operations are being determined and sent to output. In this simple program the usage of a data provider may be too much overhead, but if the program grows and you need to access the order data in many nested methods, it is as handy as global data but without the danger of overwriting it accidentally. The data provider gives also transparency about where the data comes from. Just use the where-used list and you understand that the data is provided in the constructor.
I would appreciate to know about your approaches!
*&---------------------------------------------------------------------*
*& Report ZP_TMP_DATA_PROVIDER
*&
*&---------------------------------------------------------------------*
*& Demo for SCN blog post "The global variable dilemma"
*& This report creates a list of all work center header data
*& used in a production order
*&---------------------------------------------------------------------*
report zp_tmp_data_provider.
parameters p_ordid type aufnr.
class lcl_data_provider definition.
public section.
types gty_t_operations type standard table of afvc with default key.
methods constructor importing iv_orderid type aufnr.
methods get_header returning value(rs_res) type aufk.
methods get_pp returning value(rs_res) type afko.
methods get_operations returning value(rt_res) type gty_t_operations.
private section.
data: ms_order_header type aufk,
ms_order_pp type afko,
mt_operations type gty_t_operations.
endclass.
class lcl_data_provider implementation.
method constructor.
" general order data
select single * from aufk
where aufnr = @iv_orderid
into @ms_order_header.
" production-related order data
select single * from afko
where aufnr = @iv_orderid
into @ms_order_pp.
" order operations
select * from afvc
where aufpl = @ms_order_pp-aufpl
into table @mt_operations.
endmethod.
" getter methods for read-only access to the global data
method get_header.
rs_res = ms_order_header.
endmethod.
method get_pp.
rs_res = ms_order_pp.
endmethod.
method get_operations.
rt_res = mt_operations.
endmethod.
endclass.
class lcl_app definition.
public section.
types: gty_t_crhd type standard table of crhd with default key.
methods constructor importing iv_orderid type aufnr.
methods get_workcenter_list returning value(rt_res) type gty_t_crhd.
private section.
data: mo_data_provider type ref to lcl_data_provider.
methods get_workcenter_oper
importing is_oper type afvc
returning value(rs_res) type crhd.
endclass.
class lcl_app implementation.
method constructor.
" instantiate the data provider
mo_data_provider = new #( iv_orderid ).
endmethod.
method get_workcenter_list.
" use data provider for accessing global data without being able
" to modify it
loop at mo_data_provider->get_operations( ) into data(ls_oper).
data(ls_workcenter) = get_workcenter_oper( ls_oper ).
collect ls_workcenter into rt_res.
endloop.
endmethod.
method get_workcenter_oper.
call function 'CR_WORKSTATION_READ'
exporting
id = is_oper-arbid " Work center ID
msgty = 'E' " Message type
importing
ecrhd = rs_res. " Header data
endmethod.
endclass.
start-of-selection.
cl_demo_output=>display(
new lcl_app( p_ordid )->get_workcenter_list( ) ).
No comments:
Post a Comment