© James E. McDonough 2017

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

22. State 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 State design pattern , another of the design patterns found in the GoF catalog. We will find this design pattern useful when we need to make an object behave differently based on its internal state.

One aspect of an entity is its current condition. A person can be healthy or sick. A door can be open or closed. Water can exist as solid, liquid, or gas. Another name for this condition is the state of the entity. Often we refer to the entity as being “in a state” to describe its condition, such as a person being “in a state of good health” or a door being “in a closed state.”

Not only is an entity considered in a state, but it can also transition to a different state. A healthy person can become sick. A closed door can be opened. Ice can melt.

In addition, an entity has its own behaviors, some of which are affected by its current state. For instance, a person can run and jump, but only when the person is in a healthy state; and can fight infection, but only when the person is in a sick state. Similarly, water can flow, but only when it is liquid, and can maintain a shape, but only when it is solid. Meanwhile, an entity may have other behaviors unaffected by its state, such as a person breathing, which can be done whether healthy or sick.

State Diagram

States and their transitions for an entity are often depicted in what is known as a state diagram, as illustrated in Figure 22-1, where

  • A state is represented by a circle containing the description of the state.

  • A state transition is represented by a single-headed arrow line connecting the state circles between which a transition is valid from the old state to the new state.

  • A transition condition is represented by a word or phrase to indicate what must occur for a transition from one state to another, and appears close to the state transition connecting line with which it is associated.

A447555_1_En_22_Fig1_HTML.jpg
Figure 22-1. State diagram

For example, the state diagram illustrated in Figure 22-2 represents the transition of water between the solid and liquid states.

A447555_1_En_22_Fig2_HTML.jpg
Figure 22-2. State diagram applied to ice changing state to water and water changing state to ice

The state diagram assists us in visualizing the various states and the valid transitions between them. To explore this further, let’s consider the various states applicable to an aircraft.

Aerial Maneuvers

Any large commercial airliner company has a multitude of airplanes in its fleet. At any given time, some of these airplanes will be flying, others will be on the ground taxiing to or from a runway, and still others will be on the ground stopped at an airport terminal gate loading or unloading passengers. These are only three of the possible states of the aircraft:

  • Flying

  • On tarmac

  • At gate

Some typical behaviors a captain may invoke for a commercial aircraft are

  • Take off

  • Ascend

  • Increase speed

  • Maintain level flight

  • Decrease speed

  • Descend

  • Land

  • Turn left

  • Turn right

  • Stop at gate

  • Load

  • Unload

  • Leave gate

  • Communicate with tower

  • Communicate with passengers

Some of these behaviors result in a change of state, while others are dependent on the current state, and still others are independent of the state. A state diagram to describe the states and transitions might look like the one shown in Figure 22-3.

A447555_1_En_22_Fig3_HTML.jpg
Figure 22-3. State diagram applied to commercial airplanes

Here we see the three aircraft states and all the valid transitions between them. For instance, the leave gate behavior is a valid transition from the at gate state to the on tarmac state, and is valid only for transitioning between these two states . There are no valid direct transitions between the states of at gate and flying.

The behaviors named ascend, maintain level flight, and descend seemingly apply only when the aircraft is in the flying state. Similarly, behaviors load and unload apply only when the aircraft is in the at gate state. Behaviors increase speed, decrease speed, turn left, and turn right apply equally to both the flying and on tarmac states, but not to the at gate state. Meanwhile, behaviors communicate with tower and communicate with passengers apply irrespective of the current state.

With these states influencing whether a behavior is applicable, imagine the conditional logic we would need to implement for the aircraft instance to insure that a behavior cannot be applied inappropriately, such as allowing the unload behavior to occur while the aircraft is in the flying state, or the turn right behavior to occur when the aircraft is in the at gate state. Although we may be able to write the initial logic to facilitate all the various checks and balances for the behaviors of the aircraft, this conditional logic begins to spiral out of control as we add more states and more behaviors during subsequent maintenance cycles.

Maintaining Control Over State Conditional Logic

The State design pattern is categorized by GoF with a behavioral purpose and an object scope. The intent behind this design pattern is the following:

  • Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. 1

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

  1. Context: Defines the interface of interest to clients

  2. State: Defines an interface for encapsulating the behavior associated with a particular state of Context

  3. ConcreteState: Subclasses

Each subclass implements a behavior associated with a state of the Context.

The UML class diagram for State is shown in Figure 22-4.

A447555_1_En_22_Fig4_HTML.jpg
Figure 22-4. UML class diagram for the State design pattern

Notice that the Context participant delegates a request to an instance of the State participant to which it holds a reference. Each of the ConcreteState subclass participants provides the request handling applicable to the state it represents by providing an implementation for the abstract handle method defined by the State participant.

Figure 22-5 shows this general UML class diagram describing a specific set of classes to facilitate airline service.

A447555_1_En_22_Fig5_HTML.jpg
Figure 22-5. UML class diagram for the State design pattern applied to the commercial airline scenario

As illustrated in Figure 22-5, each of the subclasses inheriting from the state class implements each of the abstract methods. In the case of the descend method, this is implemented in both the atGate and onTarmac classes as an empty method; it does nothing.

Indeed, it often becomes the case for the subclass to implement an empty behavior for many of the methods . Accordingly, a variation of this UML diagram is one where the state class provides empty implementations for each of its methods on behalf of the subclasses, leaving the subclasses to override only those behaviors for which some non-empty implementation is warranted, as illustrated in the UML class diagram shown in Figure 22-6.

A447555_1_En_22_Fig6_HTML.jpg
Figure 22-6. UML class diagram for the State design pattern applied to the commercial airline scenario, where a superclass provides empty method implementations for all methods

Notice that the state class and its methods shown in Figure 22-6, contrary to the diagram shown in Figure 22-5, are using a non-italic font, indicating that each method has an implementation, with each one implemented as an empty method. The state subclasses override only those methods where an implementation other than an empty method is warranted.

If the currentState attribute points to an instance of the atGate or onTarmac class, the command to descend does nothing since this will result in invoking the empty method implementation provided by the state superclass. On the other hand, if the currentState attribute points to an instance of the flying class, the overriding implementation for method descend provided by the flying class takes precedence.

With this arrangement, state methods implicitly provide no action unless explicitly overridden by the subclass to provide some action.

Regardless of which of the preceding UML diagrams are used to model the state design pattern, they all serve to eliminate all the conditional logic otherwise required to determine whether or not a method is applicable to a specific state. This means that as new state subclasses become necessary, it requires only that we 1) provide applicable implementations for its behaviors and 2) provide a way for the context entity to acquire one of these new subclasses as a state object.

You might recognize the similarity the state class in the diagram in Figure 22-6 has with the null object pattern from the previous chapter. In both cases, the classes provide empty implementations for each of their methods. The significant difference between them is that the state class is intended to be subclassed by a family of classes representing the various states through which an entity can transition, with each subclass overriding only those methods where a non-empty implementation is warranted, while the null object simply provides a valid reference to an object with empty implementations until such time as it can be replaced with a class that can provide non-empty implementations.

State in ABAP

The following code shows how we might implement the State design pattern UML class diagram illustrated above into functioning ABAP code, using the variation shown in Figure 22-6 where state is defined as a class providing no-action implementations for each of its methods, with each method corresponding to an action an airplane may make during normal operations. For brevity, Listing 22-1 shows only a subset of these methods.

Listing 22-1. Class state Showing All Methods Implemented as Empty Methods
class state definition.
  public section.
    methods      : leave_gate
                 , stop_at_gate
                 , take_off
                 , land
                 , turn_left
                 , turn_right
                 , ascend
                 , descend
                 , load
                 , unload
                 .
endclass.
class state implementation.
  method leave_gate.
  endmethod.
  method stop_at_gate.
  endmethod.
  method take_off.
  endmethod.
  method land.
  endmethod.
  method turn_left.
  endmethod.
  method turn_right.
  endmethod.
  method ascend.
  endmethod.
  method descend.
  endmethod.
  method load.
  endmethod.
  method unload.
  endmethod.
endclass.

The state class , shown above in Listing 22-1, plays the role of the State participant and the implementation for each of its methods is empty. This insures that no action will be taken for that corresponding command unless it is overridden in a subclass providing some specific action.

The following three classes play the role of the ConcreteState participants, each inheriting from the state class. Listing 22-2 shows the code for the class at_gate_state.

Listing 22-2. Class at_gate_state
class at_gate_state definition inheriting from state.
  public section.
    methods      : constructor
                     importing aircraft type ref to airplane
                 , leave_gate    redefinition
                 , load          redefinition
                 , unload        redefinition
                 .
  private section.
    data         : airplane       type ref to airplane.
endclass.
class at_gate_state implementation.
  method constructor.
    call method super->constructor.
    me->airplane                  = aircraft.
  endmethod.
  method leave_gate.
    call methods of me->airplane facilitating this
      method in this state
    call method me->airplane->switch_to_on_tarmac_state.
  endmethod.
  method load.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
  method unload_gate.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
endclass.

The at_gate_state class shown in Listing 22-2 overrides the definitions for only those methods of its superclass for which it intends to offer an activity other than the no-action provided by the superclass method. In this case, the only actions applicable to an airplane in the at_gate state are the leave_gate, load, and unload actions. Notice that the implementation for each of these methods invokes methods of the airplane instance, the reference to which is provided to the constructor method when this state object is instantiated.

Listing 22-3 shows the class on_tarmac_state.

Listing 22-3. Class on_tarmac_state
class on_tarmac_state definition inheriting from state.
  public section.
    methods      : constructor
                     importing aircraft type ref to airplane
                 , stop_at_gate  redefinition
                 , take_off      redefinition
                 , turn_left     redefinition
                 , turn_right    redefinition
                 .
  private section.
    data         : airplane       type ref to airplane.
endclass.
class on_tarmac_state implementation.
  method constructor.
    call method super->constructor.
    me->airplane                  = aircraft.
  endmethod.
  method stop_at_gate.
    call methods of me->airplane facilitating this
      method in this state
    call method me->airplane->switch_to_at_gate_state.
  endmethod.
  method take_off.
    call methods of me->airplane facilitating this
      method in this state
    call method me->airplane->switch_to_flying_state.
  endmethod.
  method turn_left.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
  method turn_right.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
endclass.

The on_tarmac_state class overrides the definitions for only those methods of its superclass for which it intends to offer an activity other than the no-action provided by the superclass method. In this case, the only actions applicable to an airplane in the on_tarmac state are the stop_at_gate, take_off, turn_left, and turn_right actions.

The same concept applies to the flying_state class, shown in Listing 22-4.

Listing 22-4. Class flying_state
class flying_state definition inheriting from state.
  public section.
    methods      : constructor
                     importing aircraft type ref to airplane
                 , land          redefinition
                 , turn_left     redefinition
                 , turn_right    redefinition
                 , ascend        redefinition
                 , descend       redefinition
                 .
  private section.
    data         : airplane       type ref to airplane.
endclass.
class flying_state implementation.
  method constructor.
    call method super->constructor.
    me->airplane                  = aircraft.
  endmethod.
  method land.
    call methods of me->airplane facilitating this
      method in this state
    call method me->airplane->switch_to_on_tarmac_state.
  endmethod.
  method turn_left.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
  method turn_right.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
  method ascend.
    call methods of me->airplane facilitating this
      method in this state
  endmethod .
  method descend.
    call methods of me->airplane facilitating this
      method in this state
  endmethod.
endclass.

Finally, the airplane class shown in Listing 22-5, playing the role of the Context participant, has a constructor method that creates an instance of each of the states to which it may transition . It determines the command it is to execute in its request method, the implementation for which is a case statement invoking a corresponding method of its current state object.

Listing 22-5. Class airplane
class airplane definition.
  public section.
    methods      : constructor
                 , request
                     importing action type string
                 , switch_to_at_gate_state
                 , switch_to_on_tarmac_state
                 , switch_to_flying_state
                 .
  private section.
    data         : current_state   type ref to state
                 , at_gate_state   type ref to state
                 , on_tarmac_state type ref to state
                 , flying state    type ref to state
                 .
endclass.
class airplane implementation.
  method constructor.


    create object at_gate_state
             type at_gate_state
      exporting aircraft = me.
    create object on_tarmac_state
             type on_tarmac_state
      exporting aircraft = me.
    create object flying_state
             type flying_state
      exporting aircraft = me.
    call method me->switch_to_at_gate_state.
  endmethod.
  method switch_to_at_gate_state.
    me->current_state             = me->at_gate_state.
  endmethod.
  method switch_to_on_tarmac_state.
    me->current_state             = me->on_tarmac_state.
  endmethod.
  method switch_to_flying_state.
    me->current_state             = me->flying_state.
  endmethod .
  method request.
    case action.
      when 'leave gate'.
        call method current_state->leave_gate.
      when 'stop at gate'.
        call method current_state->stop_at_gate.
      when 'take off'.
        call method current_state->take_off.
      when 'land'.
        call method current_state->land.
      when 'turn left'.
        call method current_state->turn_left.
      when 'turn right'.
        call method current_state->turn_right.
      when 'ascend'.
        call method current_state->ascend.
      when 'descend'.
        call method current_state->descend.
      when 'load'.
        call method current_state->load.
      when 'unload'.
        call method current_state->unload.
    endcase.
  endmethod.
endclass.

The actual processing associated with the action identified in the case statement of method request is dependent upon which concrete state class reference is occupying the current_state attribute. For instance, when the command unload is encountered while in the flying state, the absence of a redefinition in the flying_state class for the unload method means that the no-action method provided by the superclass will be invoked, resulting in the command being ignored.

Notice also that any state change required by the processing of the command is handled by the concrete state object; other than setting its initial state, the airplane class no longer controls setting its own state. The responsibility for changing the state of the airplane has been delegated to the corresponding concrete state objects; for example, method land of concrete state object flying_state invokes method switch_to_on_tarmac_state of its airplane instance attribute.

ABAP Class Interdependency Considerations

All of the classes defined in Listings 22-1 through 22-5 are conceptually correct, but notice that most of them are interdependent upon each other. The definition for class at_gate_state shown in Listing 22-2 has a private attribute defined as ref to type airplane, a class not defined until Listing 22-5. This same dependency applies to the on_tarmac_state class shown in Listing 22-3 and the flying_state class shown in Listing 22-4, both of which also have a private attribute defined as reference to the airplane defined in Listing 22-5. Class airplane has references to the state class defined in Listing 22-1. Indeed, the state class is the only one defined without any dependencies on any of these other classes.

It is under class interdependency scenarios such as this that we need to consider other implications of the ABAP repository that were first mentioned in Chapter 3 in the “Managing Class Encapsulation Units in the ABAP Repository” section. Let’s explore the implications this class interdependency has on the decision whether to designate the set of classes shown in Listings 22-1 through 22-5 as global classes or local classes .

Let’s start with implications of global classes. If each of the classes shown in Listings 22-1 through 22-5 were defined as a global class, then using the source code-based editor of the Class Builder, we could, with one exception, define them as they are shown in the listings above. Five distinct global classes would be defined to correspond to each of the five listings. The one exception is the name of the class. Due to the ABAP repository naming restrictions applying to custom development objects, each class needs to start with a Y or Z. Furthermore, it is customary for global classes to be defined with the leading characters CL_, meaning that all of our classes defined globally would now assume the class names zcl_state, zcl_airplane, etc. Once this is done, the classes can exist as independent entities despite their interdependency, although it may require that the interdependent classes be activated simultaneously.

With local classes it’s a different story. All of these class definitions would reside in a single ABAP repository object. Since the ABAP compiler is a single-pass compiler, it will expect to already have parsed through the source code representing a class when it encounters a reference to that class in the source code. Notice in the classes defined in Listings 22-2 through 22-4 their respective references to class airplane, which, if the classes were arranged in the sequence shown by the listings, would not yet have been by the compiler parsing the source code during its single pass. The ABAP language provides a statement that enables a class to be referenced in the code before the compiler encounters its actual definition: the class definition deferred statement, the syntax of which is

class <class name> definition deferred.

This statement needs to be placed in the source code prior to the first reference to the class . Accordingly, we would place

class airplane definition deferred.

between Listing 22-1 and Listing 22-2, so that the reference by class at_gate_state to class airplane will be accepted by the compiler, despite not yet having parsed through the code defining the airplane class, as shown in Listing 22-6.

Listing 22-6. Classes state, at_gate_state, on_tarmac_state, flying_state, and airplane with Deferred Definition Statement for Class airplane Preceding the First Class to Reference It
class state definition.
  o
  o
endclass.
class state implementation.
  o
  o
endclass.


class airplane definition deferred.

class at_gate_state definition.
  o
  o
  private section.
    data         : airplane       type ref to airplane.
endclass.
class at_gate_state implementation.
  o
  o
endclass.


class on_tarmac_state definition.
  o
  o
endclass.
class on_tarmac_state implementation.
  o
  o
endclass.


class flying_state definition.
  o
  o
endclass.
class flying_state implementation.
  o
  o
endclass.


class airplane definition.
  o
  o
endclass.
class airplane implementation.
  o
  o
endclass.

This is often sufficient to resolve references to declarations of classes defined later in a single repository object . However, this still is not enough for Listings 22-2 through 22-5. Whereas the definition deferred statement will permit the reference to a class not yet encountered by the compiler, it will not defer references to the members defined by the deferred class. Look again at Listing 22-2, where the implementation for method leave_gate of class at_gate_state invokes a method of the airplane class with the statement

call method me->airplane->switch_to_on_tarmac_state.

This means there is a reference not just to class airplane but to one of its members, and the statement will be flagged by the compiler as an invalid reference.

A way to get around this with local classes is to separate the definition portion of the class from its implementation portion, similar to the arrangement shown in Listing 22-7.

Listing 22-7. Classes state, at_gate_state, on_tarmac_state, flying_state, and airplane with Deferred Definition Statement for Class airplane Preceding the First Class to Reference It and Where Definition Portions for All Classes Precede the Implementation Portion of Any Class
class state definition.
  o
  o
endclass.


class airplane definition deferred.

class at_gate_state definition.
  o
  o
endclass.


class on_tarmac_state definition.
  o
  o
endclass .


class flying_state definition.
  o
  o
endclass.


class airplane definition.
  public section.
    methods      : constructor
                 , request
                     importing action type string
                 , switch_to_at_gate_state
                 , switch_to_on_tarmac_state
                 , switch_to_flying_state
                 .
  o
  o
endclass.


class state implementation.
  o
  o
endclass.


class at_gate_state implementation.
  o
  o
  method leave_gate.
    o
    o
    call method me->airplane->switch_to_on_tarmac_state.
  endmethod.
endclass.


class on_tarmac_state implementation.
  o
  o
endclass.


class flying_state implementation.
  o
  o
endclass.


class airplane implementation.
  o
  o
endclass.

The fact that the airplane class definition portion now precedes the at_gate_state class implementation portion now makes it possible for the at_gate_state class to reference a member of the airplane class. The example in Listing 22-7 is one where all class definition portions precede any class implementation portions. In this case, there is no reason the definition and implementation portions of class state cannot remain adjacent, since there are no references to its members by any other classes, but it is shown separated in Listing 22-7 just for consistency in keeping all class definition portions adjacent to each other. The definition deferred statement is still necessary in this arrangement because it resolves the references in classes at_gate_state, on_tarmac_state, and flying state to the class definition for airplane that the compiler still would not yet have encountered.

Summary

In this chapter, we learned the concept of state, how to create state diagrams to represent states and state transitions, how the state of a class is represented by the values of its attributes, and how instances of classes can alter their behavior as their internal state changes. The State design pattern reduces the conditional logic necessary to determine the manifestation of a behavior by encapsulating behavior implementations into state objects. It assists us in preventing subsequent maintenance cycles from spiraling out of control. We also learned two variations of providing implementations for these behaviors: one variation relies on an interface or abstract superclass to provide the definitions for all methods that are then implemented by subclasses and the other variation relies on a superclass also providing empty implementations for all of its methods, leaving the subclasses to override only those where a non-empty method is applicable.

State Exercises

Refer to Chapter 18 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 312 series: ZOOT312A through ZOOT312L.

Footnotes

1 GoF, p. 305.

2 GoF, p. 306.

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

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