© James E. McDonough 2017

James E. McDonough, Object-Oriented Design with ABAP, 10.1007/978-1-4842-2838-8_16

16. Decorator Design Pattern

James E. McDonough

(1)Pennington, New Jersey, USA

The next stop on our voyage through the Design Patterns galaxy takes us to the Decorator design pattern, another of the design patterns found in the GoF catalog. We will find this design pattern useful in dynamically applying additional responsibilities to objects.

The Decorator design pattern simplifies the task of assigning additional capabilities to an object. Many times its advantages become evident only during maintenance efforts, in situations where additional capabilities are assigned to an object that does not yet use the Decorator pattern. It addresses a problem that has become all too common as new features are added to existing software. First, let’s explore the problem it addresses, and then see how the Decorator design pattern solves the problem.

The Practical Limits of Inheritance

The current exercise program with its classes named car and truck inheriting from class vehicle has been working just fine, but now the users are requesting new capabilities. They want to be able to indicate that car and truck objects have either or both of two specific types of optional equipment:

  • Vehicle locator (VL): A high-tech device used to find the vehicle after it has been stolen

  • Cold climate (CC): A package of equipment for operating in cold weather, including such things as heavy-duty battery, fuel anti-coagulation system, upgraded deicing and defrosting unit, etc.

Our first inclination might be to change our UML class diagram to include new specialization subclasses to car and truck that include one or both of these features. This seems to be a perfectly reasonable approach since a car with a vehicle locator option represents a specialization of a car without such an option, and inheritance is well suited to reusing an existing class as the parent to one offering a more specialized level of abstraction. Our UML diagram might look like the one shown in Figure 16-1.

A447555_1_En_16_Fig1_HTML.jpg
Figure 16-1. UML diagram with hierarchy of classes facilitating two vehicle options

For these two new vehicle options features , we added three new subclasses inheriting from car and another three new subclasses inheriting from truck. We can now accommodate either a car or a truck with both features, only one feature, or neither feature. Indeed, this might work well for a while.

Sometime later we find the users returning with a request for “just one more” vehicle option:

  • Corrosion resistance (CR): An under-body coating on exposed metal parts to inhibit rust

Following the design we used to implement the previous two vehicle options, we begin by extending the UML class diagram to include new specialization subclasses to car and truck, and it is during this process where the maintenance problem inherent in this design becomes apparent. It will take eight new subclasses to handle all the unique combinations of vehicle options, as illustrated in the updated UML class diagram in Figure 16-2.

A447555_1_En_16_Fig2_HTML.jpg
Figure 16-2. UML diagram with hierarchy of classes facilitating three vehicle options

Although we might be able to handle this with some effort, the next user request for “just one more” vehicle option will require 16 new subclasses to handle all the unique combinations, and the one after that will require 32 more. At some point during maintenance it will require far too much time and effort to build the new subclasses required to accommodate all the unique combinations for “just one more” vehicle option. There is a name for this phenomenon: it is called class explosion.

Accordingly, it is during maintenance efforts where we are likely to realize that this class design based solely on inheritance is not scalable for handling subsequent requests for new vehicle options. As currently organized, we can determine the total number of classes that would be required to represent any type of vehicle with any combination of vehicle options with the formula

  • 2 n m

where the exponent n denotes the number of vehicle options we may select for a vehicle (including none at all) and m represents the different type of vehicle subclasses upon which we may apply those vehicle options. The combination of three vehicle options (vehicle locator, cold climate, and corrosion resistance) and the two different types of vehicles (car and truck) produces the requirement for the 16 subclasses to the vehicle class as illustrated in the UML class diagram in Figure 16-2. Were we to include a new type of vehicle at this point, it would require 8 more vehicle subclasses, for a total of 24. If at that point we provide a new vehicle option, it would require a total of 48 vehicle subclasses, twice the number we already have available. Imagine the effort required just to write all these new classes. It becomes a daunting task even for seasoned programmers to keep up with the development requirements for each new feature added to the mix.

Extending Functionality with Decorator

The Decorator design pattern overcomes the limitations of inheritance to support the various combinations of members of a set. In this example, the set is represented by the extra features we are able to select for the vehicles. Decorator is categorized by GoF with a structural purpose and an object scope. The intent behind this design pattern is the following:

  • Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. 1

Decorator makes use of these participants2 working in collaboration with each other:

  1. Component : Defines an interface for objects that can have responsibilities added to them dynamically.

  2. ConcreteComponent: Defines an object to which additional responsibilities can be attached.

  3. Decorator: Maintains a reference to a Component object and defines an interface that conforms to Component’s interface.

  4. ConcreteDecorator: Adds responsibilities to the component.

The UML class diagram for Decorator is shown in Figure 16-3.

A447555_1_En_16_Fig3_HTML.jpg
Figure 16-3. UML class diagram for the Decorator design pattern

Notice the relationship between decorator and component . Their cardinality indicates one decorator will have one component. This recursive relationship between decorator and component will continue indefinitely as long as the component object is a concreteDecorator, and will be terminated when a component object is a concreteComponent.

Although class inheritance is used in this design pattern, its flexibility is derived from its use of class composition. Its definitive characteristic is that the abstract decorator “has a” abstract component; a decorator class is composed of, among other things, an instance of another component class. This feature is what enables composing a series of these objects at execution time. Think of each concrete decorator as a “wrapper 3 wrapping an instance of either a concrete component or another concrete decorator. Within the implementation for each decorator of the public behavior operation, it is imperative that there be an invocation of the public behavior operation of the wrapped component, either before or after performing its own behavior, guaranteeing that each decorator in the series gets a chance to contribute its own additional responsibility.

Figure 16-4 shows this general UML class diagram describing a specific set of classes to facilitate a windowing presentation service.

A447555_1_En_16_Fig4_HTML.jpg
Figure 16-4. UML class diagram for the Decorator design pattern applied to windowing presentation service scenario

In each case above where there are multiple behaviors shown for a class, the public behavior draw invokes the private behaviors of the same class. Also, the public behavior draw implemented for both the horizontalScrollBarDecorator and verticalScrollBarDecorator will at some point invoke the public behavior draw of their wrapped component .

Let’s create some scenarios to show how the Decorator pattern would be used to facilitate attaching additional responsibilities dynamically. We’ll assume that the window frame is 10 centimeters in height and 15 centimeters in width, and that the client uses a window to display content.

  1. In this first scenario, the content of the information to be presented fits completely within the 10x15 centimeter window frame. In this case, we don’t need scroll bars, so we create an instance of class simpleWindow and provide this to the client, who then could invoke its public method draw to have the content displayed within the window frame.

  2. In this next scenario, the content of the information to be presented is 8x20. Here is a case where we need a horizontal scroll bar to be able to move the window frame left and right to see all of the content. Accordingly, we create an instance of a class simpleWindow wrapped within an instance of class horizontalScrollBarDecorator, as illustrated in Figure 16-5.

    A447555_1_En_16_Fig5_HTML.jpg
    Figure 16-5. simpleWindow wrapped by horizontalScrollBarDecorator

    The horizontalScrollBarDecorator instance is provided to the client, who then can invoke its public method draw. The implementation of draw for horizontalScrollBarDecorator first invokes the public method draw of its wrapped Component , simpleWindow. The implementation of public method draw for simpleWindow merely presents the content to be displayed and then passes control back to the caller. When control returns to the draw method of horizontalScrollBarDecorator, it invokes its private method drawHorizontalScrollBar, which causes a horizontal scroll bar to appear in the window frame, enabling the user to scroll the frame left and right to view all the content.

  3. In this next scenario, the content of the information to be presented is 12x12. Here is a case where we need a vertical bar to be able to move the window frame up and down to see all of the content. Accordingly, we create an instance of a class simpleWindow wrapped within an instance of class verticalScrollBarDecorator, as illustrated in Figure 16-6.

    A447555_1_En_16_Fig6_HTML.jpg
    Figure 16-6. simpleWindow wrapped by verticalScrollBarDecorator

    The verticalScrollBarDecorator instance is provided to the client, who then can invoke its public method draw. The implementation of draw for verticalScrollBarDecorator first would invoke the public method draw of its wrapped Component , simpleWindow. The implementation of public method draw for simpleWindow merely would present the content to be displayed and then pass control back to the caller. When control returns to the draw method of verticalScrollBarDecorator, it invokes its private method drawVerticalScrollBar, which causes a vertical scroll bar to appear in the window frame, enabling the user to scroll the frame up and down to view all the content.

  4. In this next scenario, the content of the information to be presented is 30x30. Here is a case where we need both horizontal and vertical scroll bars to be able to move the window frame left, right, up, and down to see all of the content. Accordingly, we create an instance of a class simpleWindow wrapped within an instance of class horizontalScrollBarDecorator, itself wrapped within an instance of verticalScrollBarDecorator, as illustrated in Figure 16-7.

    A447555_1_En_16_Fig7_HTML.jpg
    Figure 16-7. simpleWindow wrapped by horizontalScrollBarDecorator wrapped by verticalScrollBarDecorator

    The verticalScrollBarDecorator instance is provided to the client, who can then invoke its public method draw. The implementation of draw for verticalScrollBarDecorator first would invoke the public method draw of its wrapped Component , horizontalScrollBarDecorator, which first would invoke the public method draw of its wrapped Component, simpleWindow. The implementation of public method draw for simpleWindow merely would present the content to be displayed and then pass control back to the caller. When control returns to the draw method of horizontalScrollBarDecorator, it invokes its private method drawHorizontalScrollBar, which causes a horizontal scroll bar to appear in the window frame, enabling the user to scroll the frame left and right to view the content, and then pass control back to the caller. When control returns to the draw method of verticalScrollBarDecorator, it invokes its private method drawVerticalScrollBar, which causes a vertical scroll bar to appear in the window frame, enabling the user to scroll the frame up and down to view the content.

Notice that in each scenario the client merely invokes the draw method of the window instance it has been provided, and is oblivious to whether that instance is a concrete component or a concrete decorator. Notice also in each scenario the innermost (or only) instance is one of class simpleWindow, which does not have any component upon which to invoke any behaviors. In addition, notice that the wrapping decorator instances always wrap an object of class window, a more general abstraction level in the decorator’s very own inheritance hierarchy, and insure that the draw method of their wrapped component gets invoked, lest the wrapped component not have an opportunity to contribute the additional responsibility it attaches to the object. And finally, notice that the set of wrapped instances are built at execution time, when it can be known which decorators are required to contribute the additional responsibility necessary to enable the user to view the content.

There are two distinct phases when using the Decorator design pattern:

  • Construction

  • Execution

Construction is the process of arranging for a simpleWindow to be wrapped by a windowDecorator, which itself may be wrapped by yet another windowDecorator, a process that continues during construction until all of the necessary decorators have been included in the mix. The process begins with the instantiation of a simpleWindow. This instance is then provided as the component to be wrapped with the instantiation of a new windowDecorator, which itself is then provided as the component to be wrapped with the instantiation of yet another windowDecorator, and continues until a windowDecorator is instantiated which is not wrapped by any other windowDecorator.

Execution is the process of invoking the public operation method on the final instance created during construction. The implementation of this method in each windowDecorator component will invoke the same named method of its wrapped component, a process that will continue until the same named method of the simpleWindow instance contained at the center of these wrapping instances finally is invoked.

Wow! That is a lot to digest. Let’s put this into context with the vehicle options and see how using the Decorator pattern overcomes the limitations of inheritance . With Decorator, we do not need to define all the different combinations of vehicles and their various options as independent classes, as we tried to do until we realized that this model would result in class explosion as new options were added. We simply need to define each vehicle option as a single concrete decorator class. Then, at execution time, we compose a series of objects to represent the combination of vehicle options requested by the user. Each vehicle option becomes a decorator to the vehicle, attaching to the vehicle the additional responsibility provided by the vehicle option. The UML class diagram shown in Figure 16-8 illustrates this.

A447555_1_En_16_Fig8_HTML.jpg
Figure 16-8. UML class diagram for the Decorator design pattern applied to the vehicle options scenario

Let’s also show the roles the various class types play as participants in this design pattern:

  1. Component : vehicle

  2. ConcreteComponent: car, truck

  3. Decorator: vehicleOption

  4. ConcreteDecorator: vehicleOptionVL, vehicleOptionCC, vehicleOptionCR

The following pseudocode represents the implementation for method getGrossWeight in the concrete component:

grossWeight = thisVehicleWeight

Also, the following pseudocode represents the implementation for method getGrossWeight in the concrete decorator, where in this case it will invoke the getGrossWeight behavior of the “wrapped” component before performing this behavior itself:

invoke method getGrossWeight
              of instance wrappedVehicle
              returning grossWeight
add thisOptionWeight to grossWeight

As shown above, the implementation of getGrossWeight in the decorator class passes control on to its wrapped object, from which it receives a gross weight, and then adds its own vehicle option weight to this gross weight, which is sent back to the caller.

Suppose during execution we encounter a scenario where a user wants a truck with all three of these vehicle options. The series of classes would be composed in the following way:

  • First, we create an instance of a truck class.

  • Next, we create an instance of a vehicleOptionVL class and indicate that its wrapped vehicle is the truck object.

  • Next, we create an instance of a vehicleOptionCC class and indicate that its wrapped vehicle is the vehicleOptionVL object.

  • Finally, we create an instance of a vehicleOptionCR class and indicate that its wrapped vehicle is the vehicleOptionCC object.

When complete, the vehicleOptionCR instance “has a” vehicleOptionCC instance, which “has a” vehicleOptionVL instance, which “has a” truck instance. Stated another way, one concrete component, truck, is wrapped by a concrete decorator, vehicleOptionVL, which is wrapped by another concrete decorator, vehicleOptionCC, which is wrapped by yet another concrete decorator, vehicleOptionCR. Invoking the getGrossWeight on the vehicleOptionCR instance results in a gross weight returned that is the sum of the weight of the truck, the weight of the VL option, the weight of the CC option, and the weight of the CR option. The client merely invokes the getGross Weight of the outermost class. It does not need to know anything about the way in which the gross weight is being calculated.

The order in which these concrete decorators wrap other objects hardly matters since each decorating object will get a chance to perform its decorating behavior, but the innermost object is always a concrete component. Accordingly, we can mix and match a vehicle concrete component with various selected vehicle decorator concrete components at execution time to create what amounts to a single vehicle object with all the necessary vehicle options. This is how the Decorator design pattern helps us to avoid the pitfalls of class explosion ; we simply compose at execution time a combination of objects from those classes available to us, instead of trying during design and coding time to accommodate all the various combinations into single classes.

Decorator in ABAP

First, we will cover how to define the components participating in the Decorator design pattern, and then we will cover how to construct those components into a usable set of objects.

Defining the Decorator

Here is how we might implement the Decorator design pattern UML class diagram illustrated above into functioning ABAP code. First up, playing the role of the Component participant, is abstract class vehicle, shown in Listing 16-1.

Listing 16-1. Abstract Class vehicle
class vehicle definition abstract.
  public section.
    methods      : get_gross_weight abstract
                     returning value(gross_weight) type int4.
endclass.

Notice that the public method it defines, get_gross_weight, is abstract. Next, playing the roles of ConcreteComponent participants, are the definitions for class car, shown in Listing 16-2, and class truck, shown in Listing 16-3.

Listing 16-2. Class car
class car definition inheriting from vehicle.
  public section.
    methods      : get_gross_weight redefinition.
  private section.
    constants    : this_vehicle_weight type int4 value 2208.
endclass.
class car implementation.
  method get_gross_weight.
    gross_weight                  = me->this_vehicle_weight.
  endmethod.
endclass.
Listing 16-3. Class truck
class truck definition inheriting from vehicle.
  public section.
    methods      : get_gross_weight redefinition.
  private section.
    constants    : this_vehicle_weight type int4 value 8802.
endclass.
class truck implementation.
  method get_gross_weight.
    gross_weight                  = me->this_vehicle_weight.
  endmethod.
endclass.

Notice that both the car and truck classes inherit from the abstract class vehicle. Notice also that each one contains a constant indicating the weight of the car or truck vehicle, and that each provides an implementation for the inherited abstract method get_gross_weight, implemented to return the car or truck vehicle weight as the gross weight.

Next, shown in Listing 16-4 and playing the role of the Decorator participant, is the definition for abstract class vehicle_option.

Listing 16-4. Abstract Class vehicle_option
class vehicle_option definition abstract inheriting from vehicle.
  protected section.
    data         : wrapped_object type ref to vehicle.
endclass.

Notice for this class that it has a protected section defining field wrapped_object as a reference to a vehicle object. Furthermore, notice that it indicates method get_gross_weight also is abstract in this class, meaning that this class is deferring an implementation for this inherited abstract method to its subclasses.

Next, shown below and playing the roles of ConcreteDecorator participants, are the three classes vehicle_option_vl (vehicle locator), vehicle_option_cc (cold climate), and vehicle_option_cr (corrosion resistance). Each inherits from class vehicle_option and each provides an implementation for the abstract method get_gross_weight. In addition, each has a constructor method that accepts a reference to a vehicle object it is to wrap. Each class also contains a constant holding the weight for the particular vehicle option. Listing 16-5 shows the code for class vehicle_option_vl.

Listing 16-5. Class vehicle_option_vl
class vehicle_option_vl definition inheriting from vehicle_option.
  public section.
    methods      : get_gross_weight redefinition
                 , constructor
                     importing object_to_wrap type ref to vehicle
                 .
  private section.
    constants    : this_option_weight type int4 value 55.
endclass.
class vehicle_option_vl implementation.
  method constructor.
    call method super->constructor.
    me->wrapped_object            = wrapped_object.
  endmethod.
  method get_gross_weight.
    gross_weight = me->wrapped_object->get_gross_weight( ).
    add me->this_option_weight to gross_weight.
  endmethod.
endclass.

Notice that the constructor method takes the reference it is provided for the object it is to wrap and places this into its own field wrapped_object, a field it inherits from its superclass. Accordingly, each ConcreteDecorator has a reference to the vehicle instance it is wrapping. Notice also that in its implementation for the get_gross_weight method it first invokes the get_gross_weight method of the object it is wrapping, receiving from it a gross weight value, and then adds its own vehicle option weight to this gross weight.

Compare the definition for class vehicle_option_vl in Listing 16-5 with class vehicle_option_cc shown in Listing 16-6. They are identical with the exception of the class name and the value specified for the option weight.

Listing 16-6. Class vehicle_option_cc
class vehicle_option_cc definition inheriting from vehicle_option.
  public section.
    methods      : get_gross_weight redefinition
                 , constructor
                     importing wrapped_object type ref to vehicle
                 .
  private section.
    constants    : this_option_weight type int4 value 404.
endclass.
class vehicle_option_cc implementation.
  method constructor.
    call method super->constructor.
    me->wrapped_object            = wrapped_object.
  endmethod.
  method get_gross_weight.
    gross_weight = me->wrapped_object->get_gross_weight( ).
    add me->this_option_weight to gross_weight.
  endmethod.
endclass.

Class vehicle_option_cr, shown in Listing 16-7, also is identical to the previous two classes other than the same exceptions.

Listing 16-7. Class vehicle_option_cr
class vehicle_option_cr definition inheriting from vehicle_option.
  public section.
    methods      : get_gross_weight redefinition
                 , constructor
                     importing wrapped_object type ref to vehicle
                 .
  private section.
    constants    : this_option_weight type int4 value 26.
endclass.
class vehicle_option_cr implementation.
  method constructor.
    call method super->constructor.
    me->wrapped_object            = wrapped_object.
  endmethod.
  method get_gross_weight.
    gross_weight = me->wrapped_object->get_gross_weight( ).
    add me->this_option_weight to gross_weight.
  endmethod.
endclass.

At this point, we have ABAP code examples for all of the participants of the Decorator design pattern described by the preceding UML. However, this only facilitates the execution of the pattern. We still need to assemble these objects into a set of wrapped components.

Constructing the Decorator

The UML diagram in Figure 16-8, for which we have been providing all this code, has an entity called client, which is shown using the Component of this pattern, class vehicle. The client uses the vehicle by invoking its get_gross_weight method. Before this can occur, we need a mechanism to construct the various participants into a usable state. This may be done by the client or by some other component on behalf of the client. The code shown in Listing 16-8 provides an example of how we might define a class that can construct a decorated vehicle for a caller that can indicate the type of vehicle as well as the set of decorators with which it is to be adorned.

Listing 16-8. Definition of Class to Construct the Decorated Vehicle
class vehicle_builder definition abstract final.
  public section.
    class-methods: build_car
                     importing
                       vehicle_locator_option      type abap_bool
                       cold_climate_option         type abap_bool
                       corrosion_resistance_option type abap_bool
                     exporting
                       car type ref to vehicle
                 , build_truck
                     importing
                       vehicle_locator_option      type abap_bool
                       cold_climate_option         type abap_bool
                       corrosion_resistance_option type abap_bool
                     exporting
                       truck type ref to vehicle
                 .
  private section.
    class-methods: apply_options
                     importing
                       basic_vehicle type ref to vehicle
                       vehicle_locator_option      type abap_bool
                       cold_climate_option         type abap_bool
                       corrosion_resistance_option type abap_bool
                     exporting
                       vehicle type ref to vehicle
                 .
endclass.

The definition of this static class, shown in Listing 16-8, simply provides two public static methods, one to create a car and one to create a truck, accepting flags indicating whether each of the possible vehicle options is to be included. The private static method facilitates applying the specified options.

The implementation of this class is presented in Listing 16-9.

Listing 16-9. Implementation of Class to Construct the Decorated Vehicle
class vehicle_builder implementation.
  method build_car.
    data         : basic_car type ref to vehicle.
    create object basic_car type car.
    call method vehicle_builder=>apply_options
      exporting
        basic_vehicle               = basic_car
        vehicle_locator_option      = vehicle_locator_option
        cold_climate_option         = cold_climate_option
        corrosion_resistance_option = corrosion_resistance_option
      importing
        vehicle                     = car.
  endmethod.
  method build_truck.
    data         : basic_truck type ref to vehicle.
    create object basic_truck type truck.
    call method vehicle_builder=>apply_options
      exporting
        basic_vehicle               = basic_truck
        vehicle_locator_option      = vehicle_locator_option
        cold_climate_option         = cold_climate_option
        corrosion_resistance_option = corrosion_resistance_option
      importing
        vehicle                     = truck.
  endmethod.
  method apply_options.
    data         : options_list type standard table of seoclsname
                 , option like line of options_list
                 , object_to_be_wrapped type ref to vehicle
                 .
    if vehicle_locator_option eq abap_true.
      append 'VEHICLE_OPTION_VL' to options_list.
    endif.
    if cold_climate_option eq abap_true.
      append 'VEHICLE_OPTION_CC' to options_list.
    endif.
    if corrosion_resistance_option eq abap_true.
      append 'VEHICLE_OPTION_CR' to options_list.
    endif.
    vehicle = basic_vehicle.
    loop at options_list
       into option.
      object_to_be_wrapped = vehicle.
      create object vehicle type (option)
        exporting wrapped_object = object_to_be_wrapped.
    endloop.
  endmethod.
endclass.

Each of the public methods creates its respective car or truck instance. Each of these methods then invokes the apply_options method, which simply makes a list of the class names applicable to the vehicle options chosen, and then loops through this table creating instances of these classes, each one wrapping the initial car or truck instance and then the vehicle_option instance created during any previous loops.

Listing 16-10 describes a fragment of code showing how a client might use this class to create for it a decorated vehicle.

Listing 16-10. Code to Use Class vehicle_builder to Create an Instance of a Decorated Car
  o
  o
  data         : rental_car type ref to vehicle
               , rental_car_gross_weight type int4
               .
  o
  o
  call method vehicle_builder=>build_car
    exporting
      vehicle_locator_option      = abap_true
      cold_climate_option         = abap_true
      corrosion_resistance_option = abap_true
    importing
      vehicle                     = rental_car.
  o
  o

Listing 16-11 shows how to use the instance of vehicle it creates to find its gross_weight.

Listing 16-11. Code to Use the Decorated vehicle_builder Instance to Get Its Gross Weight
  o
  o


  rental_car_gross_weight         = rental_car->get_gross_weight( ).

After the client makes use of the vehicle_builder class shown above to provide it with an instance of a vehicle in its field rental_car, it may remain completely oblivious to the dynamic type of the instance it has been provided. The client is unconcerned with the actual type of class used to instantiate the outer wrapper of all the instances used in constructing this decorated vehicle; it simply regards it as an instance of the static type offered by the field holding its reference. We see this with the statement above invoking method get_gross_weight of the rental_car, a behavior available to an instance of vehicle; that is, while the dynamic type of the rental_car may be vehicle_option_cr, it is referenced simply as static type vehicle.

Variation on Decorator Theme

A closer examination of the implementations shown above of method get_gross_weight for those classes inheriting from class vehicle_option reveals that they are identical. In a case like this, we might consider having the superclass vehicle_option itself provide the implementation for this method on behalf of the subclasses. We can do this, but we also need to consider that the implementation of this method can use only those attributes available to it. In their current implementations, the methods refer to their constant this_option_weight, a field provided only by each vehicle_option subclass. To elevate this processing to the superclass, we need a way to inform the superclass the value for the weight of the specific option, known only to the subclass.

This can be achieved easily by providing the superclass with a protected data field into which the value for the weight of the specific option is moved during processing of the constructor method. Listing 16-12 shows how to change the abstract class vehicle_option code to facilitate this, with differences from Listing 16-4 highlighted.

Listing 16-12. Abstract class vehicle_option, Which Also Provides Implementation of Methods for Its Subclasses
class vehicle_option definition abstract inheriting from vehicle.
  public section.
    methods      : get_gross_weight redefinition.
  protected section.
    data         : wrapped_object type ref to vehicle.
    data         : option_weight  type int4.
endclass.
class vehicle_option implementation.
  method get_gross_weight.
    gross_weight = me->wrapped_object->get_gross_weight( ).
    add me->option_weight to gross_weight.
  endmethod.
endclass.

In Listing 16-12 we see the new protected data field that will hold the option weight applicable to the subclass. Notice that the class now provides an implementation for method get_gross_weight, which has been elevated from its subclasses to this superclass, but has its add statement changed to refer to the protected data field option_weight defined by this class instead of the constant provided by the subclass, since the subclass constant would be unavailable to the superclass.

Using subclass vehicle_option_vl as a proxy for all of the subclasses inheriting from class vehicle_option, we see that its redefinition of method get_gross_weight has been removed, now that this functionality is being provided by the superclass itself. Listing 16-13 shows how to change the code from Listing 16-5 to facilitate the implementation of method get_gross_weight in the superclass.

Listing 16-13. Class vehicle_option_vl Now with an Implementation for Method get_gross_weight Provided by the Superclass
class vehicle_option_vl definition inheriting from vehicle_option.
  public section.
    methods      : get_gross_weight redefinition
                 , constructor
                     importing object_to_wrap type ref to vehicle
                 .
  private section.
    constants    : this_option_weight type int4 value 55.
endclass.
class vehicle_option_vl implementation.
  method constructor.
    call method super->constructor.
    me->wrapped_object            = wrapped_object.
    me->option_weight             = me->this_option_weight .
  endmethod.
  method get_gross_weight.
    call method me->wrapped_object->get_gross_weight
      importing gross_weight.
    add me->this_option_weight to gross_weight.
  endmethod.
endclass.

In addition, the constructor sets option_weight, the protected field provided by the superclass, to the value of the this_option_weight constant defined in the subclass. This is how the superclass has access to the weight of the specific vehicle option; although it cannot reference the constants defined in the subclasses, it certainly can reference a protected field it defines on behalf of those subclasses.

As long as all subclasses of vehicle_option require the same implementation for get_gross_weight, then all of them can simply rely on the superclass vehicle_option to provide it. In those cases where a subclass requires some other processing, it is a simple matter for that subclass to override the superclass implementation and provide its own applicable processing.

Uses of the Decorator Pattern in Technology Today

Open any web page using your favorite Internet browser. A drop-down context menu will appear by right-clicking the mouse pointer on the blank area at the top of the Internet browser window. This context menu usually includes some entries with such titles as Menu Bar, Favorites Bar, Command Bar, Status Bar, and Bookmarks Toolbar. These entries typically have a check mark appearing to the left of them to indicate whether or not the corresponding feature appears within the Internet browser window, and can be toggled on and off simply by clicking their corresponding entry on this drop-down menu.

It is easy to imagine a Decorator design pattern at work controlling the presentation of the Internet browser window. The main window itself is defined by a class representing the concrete component. Each of these additional features, controlled by a click on the drop-down menu, is defined by a class representing a concrete decorator. Each time you click an entry to include or exclude it from the Internet browser window, the supporting software goes through a process of rebuilding the composition of class instances corresponding to the active selections that need to appear on the window.

Try this and see how your Internet browser window is altered as you change these settings. Consider how simple it might be for a new menu option to be included in the drop-down list and for its corresponding class to be included in the Decorator pattern controlling the appearance of the Internet browser window. In many cases, the classes corresponding to these features may be browser add-ons or plug-ins supplied by third-party software vendors complying with public method signatures and established protocols to insure compatibility with the browser software. Indeed, in using a Decorator design pattern, the designers of the software controlling the Internet browser window have assured the capability for “attach[ing] additional responsibilities to an object dynamically,” to the extent that there is no need even to know all the classes that eventually might become involved in the presentation, exemplifying the idea of “provid[ing] a flexible alternative to subclassing for extended functionality.”

Summary

In this chapter, we learned that there are practical limitations to using class inheritance , often resulting in class explosion , a scourge of software design not often discovered until the initial design has gone to production and we find ourselves faced with subsequent maintenance challenges. Since the Decorator design pattern relies on class composition for combining selected capabilities dynamically, it avoids the pitfalls associated with the class explosion evolving out of designs based primarily on inheritance. We now know there are two phases associated with the use of the Decorator design pattern: one phase for the construction of the pattern and another phase for the execution of the pattern.

Decorator Exercises

Refer to Chapter 13 of the functional and technical requirements documentation (see Appendix B) for the accompanying ABAP exercise programs associated with this chapter. Take a break from reading the book at this point to reinforce what you have read by changing and executing the corresponding exercise programs. The exercise programs associated with this chapter are those in the 306 series: ZOOT306A through ZOOT306C.

Footnotes

1 GoF, p. 175.

2 GoF, p. 177.

3 GoF offers Wrapper as the name by which the Decorator is “also known as;” GoF, p. 175.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset