© James E. McDonough 2017

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

15. Adapter 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 Adapter design pattern , another of the design patterns found in the GoF catalog. We will find this design pattern most useful once the maintenance cycle has begun on software components.

Since a software component spends most of its life in the maintenance phase, it is inevitable that changes will need to be applied to existing components. Often times a developer will recognize that some new capability recently becoming available makes a better solution than the current solution. This could result from a variety of reasons, amongst them improved performance, improved reliability, the need to port existing software to a new platform, a change in the underlying relational database, a new data exchange protocol, enhanced data security or some other disruptive technology presenting a new business opportunity to be exploited. As is often the case with maintenance programming, we would endeavor to make as few changes as necessary to enable the software with the enhanced capability.

Change is Inevitable

Suppose we have a program written years ago containing many locations at which requests for a service are made to a service provider. The original service provider expects its requesters to use the “wedge” interface, so named because of its interface shaped like a wedge, as illustrated on the left side of Figure 15-1. If we were to move the graphics on the left toward each other so they touch, as illustrated on the right side of Figure 15-1, we would find that the “point” of the service requester fits perfectly into the “notch” of the service provider, resulting in a tight fit. If these had been bricks used in building a brick wall, they would form a solid bond upon which other bricks could be laid.

A447555_1_En_15_Fig1_HTML.jpg
Figure 15-1. Illustration of wedge interface used by both service requester and service provider

Let’s suppose a new more robust service provider has become available, and we have been given the task of changing an existing program to replace requests to the old service provider with requests to the new service provider. The challenge we face is that the new service provider does not recognize the “wedge” interface we have been using with the old service provider – instead, it uses the “semicircle” interface, so named because of its interface shaped like a semicircle, as illustrated on the left side of Figure 15-2. If we were to move the graphics on the left toward each other so they touch, as illustrated on the right side of Figure 15-2, we would find that the “point” of the service requester does not make a secure fit into the “semicircle depression” of the service provider. If these had been bricks used in building a brick wall, they would not form a solid bond, and our attempt to lay other bricks upon them in their current alignment would compromise the stability and integrity of our brick wall.

A447555_1_En_15_Fig2_HTML.jpg
Figure 15-2. Illustration of wedge interface used by service requester and semicircle interface used by service provider

One solution we might consider is to embark on the task of changing all the callers to the service provider from using the “wedge” interface to using the “semicircle” interface. When there are only a few locations where a request is placed to the service provider, this approach seems reasonable. However, when we find there are many such locations , then the prospect of applying the same type of change to all those locations becomes much more involved, perhaps even daunting.

Minimizing the Effects of Change

Another solution we might consider is one where we let the service requester continue to use the “wedge” interface, but place between the service requester and the new service provider, which now expects the use of the “semicircle” interface, a component capable of resolving the differences between the interfaces. That is, the component converts the “wedge” interface used by the service requester into the “semicircle” interface expected by the service provider, as illustrated on the left side of Figure 15-3 1 where the intervening gray component facilitates the different interfaces.

A447555_1_En_15_Fig3_HTML.jpg
Figure 15-3. Illustration of adapter providing notch for wedge interface used by service requester and bump for semicircle interface used by service provider

If we were to move the graphics on the left toward each other so they touch, as illustrated on the right side of Figure 15-3, we would find that the “point” of the service requester fits perfectly into the “notch” of the adapter, and that the “semicircle bump” of the adapter fits perfectly into the “semicircle depression” of the service provider, resulting in a tight fit across all 3 components.

This may be the simpler change as it requires only that the service requester change the object it is invoking and not the interface it is using to make the invocation. The service requester interacts with a new interface adapter component, and the adapter component interacts with the service provider, effectively enabling components with dissimilar interfaces to work together through an intermediary component.

Adapting to Change

The Adapter design pattern is categorized by GoF with a structural purpose. Though all other design patterns covered by GoF have either a class scope or an object scope , Adapter is the only one applicable to both of these scopes. The class Adapter uses inheritance while the object Adapter uses composition . It should be noted that the conventional wisdom within the object-oriented programming community now considers a design based on class composition to be superior and more flexible than one based on class inheritance .

The intent behind this design pattern is the following:

  • Converts the interface of a class into another interface the client expects. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces. 2

Adapter makes use of these participants3 working in collaboration with each other:

  1. Target : Defines the domain-specific interface that Client uses.

  2. Client: Collaborates with objects conforming to the Target interface.

  3. Adaptee : Defines an existing component that needs adapting.

  4. Adapter: Adapts the interface of Adaptee to the Target interface.

The corresponding UML class diagrams for both the class adapter and object adapter are shown in Figure 15-4.4

A447555_1_En_15_Fig4_HTML.jpg
Figure 15-4. UML class diagrams showing both the class and object variations of the Adapter design pattern

In the class adapter example, the call by adapter to methodB is a call to a method provided to adapter through inheritance (adapter “is a” adaptee ). With this arrangement, the adapter inherits from the adaptee.

In the object adapter example, the call by adapter to adaptee.methodB is a call to a method of an instance contributing to the composition of adapter (adapter “has a” adaptee). With this arrangement, the adapter “wraps” an instance of the adaptee.

Notice that in the diagrams for both variations shown in Figure 15-4 the Client participant remains the same – its use of the Adapter design pattern does not require it to know whether the Target participant is using the class scope or object scope implementation. Indeed, the Client participant typically remains oblivious to the fact that the Target participant has implemented an adapter.

Adaptive Behavior

Let’s see an example of how this would work in practice. The Liberty Bell Tours Company operates a single tour bus in Philadelphia, Pennsylvania. The bus is outfitted with an Aquarius brand navigation mechanism, and this has been working well now for some years. The owners of the company recently became aware of the new Capricorn brand navigation mechanism, which offers much more reliability and less periodic maintenance than Aquarius. They decided to replace the Aquarius unit with a Capricorn unit during the next scheduled maintenance of the bus.

Aquarius supports the NavigationUnit interface, which enables tracking the direction the bus is heading based on the four main compass points: north, south, east and west. Accordingly, the bus had been configured to make use of the Aquarius unit through the NavigationUnit interface.

The new Capricorn unit is based not on a heading but on a bearing, a number of degrees of a circle between 00 and 360 to represent the direction of travel. Capricorn does not support the NavigationUnit interface and provides no support for compass point headings. So, in order for it to be used with the tour bus of the Liberty Bell Tours Company, there would need to be a way for the heading used by the bus to be converted to and from the bearing used by Capricorn. The correlation between a compass point heading and its equivalent bearing is as follows:

  • A heading of north is equivalent to a bearing of 00 degrees.

  • A heading of east is equivalent to a bearing of 90 degrees.

  • A heading of south is equivalent to a bearing of 180 degrees.

  • A heading of west is equivalent to a bearing of 270 degrees.

Rather than convert all the existing software for the bus to facilitate bearings, we have decided to write an adapter class which can accept the heading requests made by the bus and convert them into the corresponding bearing requests required by Capricorn. When a request is made for the current heading, the adapter will request from Capricorn the current bearing and then change this value in degrees into a corresponding compass heading to send back to the bus.

Figure 15-5 shows the corresponding UML class diagram for the class scope of the Adapter in the context of the Capricorn navigation unit.

A447555_1_En_15_Fig5_HTML.jpg
Figure 15-5. UML class diagram for the class scope of the Adapter design pattern in the context of the Capricorn navigation unit

Figure 15-6 shows the corresponding UML class diagram for the object scope of the Adapter in the context of the Capricorn navigation unit.

A447555_1_En_15_Fig6_HTML.jpg
Figure 15-6. UML class diagram for the object scope of the Adapter design pattern in the context of the Capricorn navigation unit

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

  1. Target : navigationUnit

  2. Client: bus

  3. Adapter: capricornAdapter

  4. Adaptee : capricorn

Regardless which of these variations we opt to use, we find the Client participant in this design pattern having a reference to a navigationUnit, the Target participant. This Target participant interface is implemented by the capricornAdapter concrete class, representing the Adapter participant. The capricornAdapter either “is a” (class adapter) or “has a” reference to (object adapter) capricorn, the Adaptee participant.

Adapter in ABAP

Let’s explore how we might implement the Adapter design pattern UML class diagrams illustrated above in Figure 15-5 (class adapter) and Figure 15-6 (object adapter) into functioning ABAP code. First, let’s establish the code as it would exist prior to the need to use the Adapter design pattern, as shown in Listing 15-1, which shows interface navigation_unit being implemented by class aquarius .

Listing 15-1. Interface navigation_unit is implemented by class aquarius
interface navigation_unit.
  types          : compass_point  type char1.
  constants      : north          type compass_point value 'N'
                 , south          type compass_point value 'S'
                 , east           type compass_point value 'E'
                 , west           type compass_point value 'W'
                 .
  methods        : get_heading
                     exporting heading type compass_point
                 , set_heading
                     importing heading type compass_point
                 .
endinterface.


class aquarius definition.
  public section.
    interfaces   : navigation_unit.
    aliases      : get_heading for navigation_unit∼get_heading
                 , set_heading for navigation_unit∼set_heading
                 .
  private section.
    data         : heading type navigation_unit=>compass_point.
endclass.
class aquarius implementation.
  method get_heading.
    heading                       = me->heading.
  endmethod.
  method set_heading.
    me->heading                   = heading.
  endmethod.
endclass.

In the example in Listing 15-1, interface navigation_unit defines two methods to be implemented by those classes implementing this interface, and class aquarius is shown implementing the navigation_unit interface and providing implementations for those methods. Listing 15-2 shows a bus class whose constructor method creates for it an instance of an Aquarius type of navigation_unit and whose head_north method invokes the set_heading method of the Aquarius navigation unit.

Listing 15-2. Class bus creates and uses an instance of aquarius via interface navigation_unit
class bus definition.
  public section.
    methods      : constructor
                 , head_north
                 .
  private section.
    data         : navigation_device type ref to navigation_unit.
endclass.
class bus implementation.
  method constructor.
    create object me->navigation_device type aquarius.
  endmethod.
  method head_north.
    call method me->navigation_device->set_heading
      exporting heading           =  navigation_unit=>north.
  endmethod.
endclass.

This is how the code would look when first placed into production.

Eventually we would get a chance to change the bus class so that it now uses the Capricorn navigation unit instead of the Aquarius unit. The class representing the Capricorn navigation unit is shown in Listing 15-3.

Listing 15-3. Class capricorn
class capricorn definition.
  public section.
    types        : degrees        type n length 3.
    methods      : get_bearing
                     exporting bearing type degrees
                 , set_bearing
                     exporting bearing type degrees
  private section.
    data         : bearing type degrees.
endclass.
class capricorn implementation.
  method get_bearing.
    bearing                       = me->bearing.
  endmethod.
  method set_bearing.
    me->bearing                   = bearing.
  endmethod.
endclass.

The capricorn class plays the role of the Adaptee participant and is intended to replace the aquarius class. Typical for an Adaptee participant is that it knows nothing about its use in the Adapter design pattern. Notice that it is based on bearings and not headings. Accordingly, the only way it can replace class aquarius is for it to be fitted with an adapter capable of converting between headings and bearings.

Adapter in ABAP Using class Scope

Since Capricorn does not facilitate navigation via compass points, but uses bearings instead, here is an example of how we would change the ABAP code to introduce the class scope variation of the adapter.

Interface navigation_unit shown in Listing 15-1, now to play the role of of the Target participant, requires no changes for use in the class scope variation of the Adapter design pattern.

The aquarius class is replaced by the capricorn_adapter class, shown in Listing 15-4, which plays the role of the Adapter participant. Though the entire class is new, only those lines that differ from the aquarius class it is replacing, described in Listing 15-1, are shown with highlighting. Notice that this class both implements the navigation_unit interface (as did the aquarius class) as well as inherits from class capricorn. This adapter class accommodates converting from headings used by the caller to bearings used by superclass capricorn.

Listing 15-4. Class capricorn_adapter; differences with aquarius class defined in Listing 15-1 shown highlighted
class capricorn_adapter definition inheriting from capricorn.
  public section.
    interfaces   : navigation_unit.
    aliases      : get_heading for navigation_unit∼get_heading
                 , set_heading for navigation_unit∼set_heading
                 .
  private section.
    constants    : bearing_north  type capricorn=>degrees value 000
                 , bearing_east   type capricorn=>degrees value 090
                 , bearing_south  type capricorn=>degrees value 180
                 , bearing_west   type capricorn=>degrees value 270
                 .
    data         : heading type navigation_unit=>compass_point.
endclass.
class capricorn_adapter implementation.
  method get_heading.
    data         : bearing        type capricorn=>degrees.
    call method me->get_bearing
      importing bearing           = bearing.
    " convert capricorn bearing to corresponding compass heading:
    case bearing.
      when capricorn_adapter=>bearing_north.
        heading                   = navigation_unit=>north.
      when capricorn_adapter=>bearing_south.
        heading                   = navigation_unit=>south.
      when capricorn_adapter=>bearing_east.
        heading                   = navigation_unit=>east.
      when capricorn_adapter=>bearing_west.
        heading                   = navigation_unit=>west.
    endcase.
  endmethod.
  method set_heading.
    data         : bearing        type capricorn=>degrees.
    " convert compass heading to corresponding capricorn bearing:
    case heading.
      when navigation_unit=>north.
        bearing                   = capricorn_adapter=>bearing_north.
      when navigation_unit=>south.
        bearing                   = capricorn_adapter=>bearing_south.
      when navigation_unit=>east.
        bearing                   = capricorn_adapter=>bearing_east.
      when navigation_unit=>west.
        bearing                   = capricorn_adapter=>bearing_west.
    endcase.
    call method me->set_bearing
      importing bearing           = bearing.
  endmethod.
endclass.

Notice that the capricorn_adapter class provides constants to represent the correlation between the four main compass points and their equivalent bearing values. Notice also that its implementations for the get_heading and set_heading methods includes case statements to determine the bearing equivalent to a heading. With this class scope adapter arrangement of these components we can say that class capricorn_adapter “implements a” interface navigation_unit and “is a” class capricorn.

Listing 15-5 shows the bus class, playing the role of the Client participant, with changes to use class capricorn_adapter instead of class aquarius shown highlighted. None of the method invocations to the navigation device require changing even though the class recording the heading – capricorn – is based not on headings but on bearings.

Listing 15-5. Class bus, with differences from Listing 15-2 shown highlighted
class bus definition.
  public section.
    methods      : constructor
                 , head_north
                 .
  private section.
    data         : navigation_device type ref to navigation_unit.
endclass.
class bus implementation.
  method constructor.
    create object me->navigation_device type capricorn_adapter.
  endmethod.
  method head_north.
    call method me->navigation_device->set_heading
      exporting heading           =  navigation_unit=>north.
  endmethod.
endclass.

Adapter in ABAP Using object Scope

Here is an example of how we would change the ABAP code to introduce the object scope variation of the adapter. Again we have the same capricorn class shown in Listing 15-3 still playing the role of the Adaptee participant as we had with the class scope example, and again the navigation_unit interface shown in Listing 15-1 still playing the role of the Target participant.

As illustrated in Listing 15-6, the aquarius class is replaced with the capricorn adapter class, still playing the role of the Adapter participant. Though the entire class is new, only those lines that differ from the aquarius class described in Listing 15-1 are shown with highlighting. The significant difference between this object scope adapter and the corresponding class scope adapter shown in Listing 15-4 is that the object scope adapter does not inherit from class capricorn, but is composed with an instance of a capricorn class.

Listing 15-6. Class capricorn_adapter; differences with aquarius class defined in Listing 15-1 shown highlighted
class capricorn_adapter definition.
  public section.
    interfaces   : navigation_unit.
    aliases      : get_heading for navigation_unit∼get_heading
                 , set_heading for navigation_unit∼set_heading
                 .
    methods      : constructor
                 .
  private section.
    constants    : bearing_north  type capricorn=>degrees value 000
                 , bearing_east   type capricorn=>degrees value 090
                 , bearing_south  type capricorn=>degrees value 180
                 , bearing_west   type capricorn=>degrees value 270
                 .
    data         : capricorn_unit type ref to capricorn.
    data         : heading type navigation_unit=>compass_point.


endclass.
class capricorn_adapter implementation.
  method constructor.
    create object capricorn_unit.
  endmethod.
  method get_heading.
    data         : bearing        type capricorn=>degrees.
    call method me->capricorn_unit->get_bearing
      importing bearing           = bearing.
    " convert capricorn bearing to corresponding compass heading:
    case bearing.
      when capricorn_adapter=>bearing_north.
        heading                   = navigation_unit=>north.
      when capricorn_adapter=>bearing_south.
        heading                   = navigation_unit=>south.
      when capricorn_adapter=>bearing_east.
        heading                   = navigation_unit=>east.
      when capricorn_adapter=>bearing_west.
        heading                   = navigation_unit=>west.
    endcase.
  endmethod.
  method set_heading.
    data         : bearing        type capricorn=>degrees.
    " convert compass heading to corresponding capricorn bearing:
    case heading.
      when navigation_unit=>north.
        bearing                   = capricorn_adapter=>bearing_north.
      when navigation_unit=>south.
        bearing                   = capricorn_adapter=>bearing_south.
      when navigation_unit=>east.
        bearing                   = capricorn_adapter=>bearing_east.
      when navigation_unit=>west.
        bearing                   = capricorn_adapter=>bearing_west.
    endcase.
    call method me->capricorn_unit->set_bearing
      importing bearing           = bearing.
  endmethod.
endclass.

Notice that whereas the class scope adapter version shown in Listing 15-4 invokes method get_bearing of capricorn via the statement

call method me->get_bearing ...

the object scope adapter version shown in Listing 15-6 invokes it via the statement

call method me->capricorn_unit->get_bearing ...

The same concept applies to calling method set_bearing. Notice also that the object scope adapter has a constructor defined for it, which will create an instance of class capricorn into its private attribute capricorn_unit. With this object scope adapter arrangement of these components we can say that class capricorn_adapter “implements a” interface navigation_unit and “has a” class capricorn. Compare this with the class scope adapter which also “implements a” interface navigation_unit but “is a” class capricorn.

The code for the bus class, shown in Listing 15-5, still plays the role of the Client participant for the object scope of the Adapter class, and requires no changes from those shown in Listing 15-5.

A Variation on a Theme

Let us suppose the navigation_unit interface defined in Listing 15-1 had been defined not as an interface but as an abstract class, as shown in Listing 15-7.

Listing 15-7. Abstract class navigation_unit is inherited by class aquarius
class navigation_unit definition abstract.
  public section.
    types        : compass_point  type char1.
    constants    : north          type compass_point value 'N'
                 , south          type compass_point value 'S'
                 , east           type compass_point value 'E'
                 , west           type compass_point value 'W'
                 .
    methods      : get_heading abstract
                     exporting heading type compass_point
                 , set_heading abstract
                     importing heading type compass_point
                 .
endclass.


class aquarius definition inheriting from navigation_unit.
  public section.
    methods      : get_heading redefinition
                 , set_heading redefinition
                 .
  private section.
    data         : heading type navigation_unit=>compass_point.
endclass.
class aquarius implementation.
  method get_heading.
    heading                       = me->heading.
  endmethod.
  method set_heading.
    me->heading                   = heading.
  endmethod.
endclass.

Other than the navigation_unit entity now defined as an abstract class with abstract methods instead of an interface, and the public section of the aquarius class changed accordingly to reflect the absence of the interfaces and aliases statements and the presence of the inheriting from phrase on the class aquarius definition statement, the code is basically the same as shown in Listing 15-1. This could just as easily have been the original way the code had been placed into production for the Liberty Bell Tours Company.

With navigation_unit defined as an abstract class as shown in Listing 15-7 instead of as an interface as shown in Listing 15-1, could we have followed either of the component relationships described by the class scope adapter UML diagram in Figure 15-5 and the object scope adapter UML diagram in Figure 15-6 in replacing the aquarius class with the capricorn_adapter class?  Specifically, would both adapter scope variations – the class adapter and the object adapter – still be available to us as options?

Here is a hint: Notice in Listing 15-7 that class aquarius includes the phrase inheriting from navigation_unit on its class definition statement.

Here is another hint: Notice in Listing 15-4 that class capricorn_adapter, modeled using the class scope adapter, includes the phrase inheriting from capricorn on its class definition statement.

Here is yet another hint: Class capricorn_adapter is intended to replace class aquarius.

The answer to the question whether both adapter scope variations still would be available to us as options is No. The class scope variation would not be available because this would require class capricorn_adapter to inherit from both class navigation_unit as well as from class capricorn. Since ABAP does not support multiple inheritance , the class scope variation cannot be used when, as shown in Listing 15-7, class aquarius already inherits from class navigation_unit. Meanwhile, the object scope variation could be used since even though it still would require class capricorn_adapter to inherit from class navigation_unit to replace aquarius it would not require it also to inherit from class capricorn – instead, an instance of class capricorn would be composed as part of class capricorn_adapter. In short, when class aquarius “implements a” navigation_unit interface, as shown in Listing 15-1, then either the class scope or object scope adapter may be used with its replacement class capricorn_adapter; but when class aquarius “is a” class navigation_unit, as shown in Listing 15-7, then only the object scope adapter may be used with its replacement class capricorn_adapter.

Summary

In this chapter we learned that our maintenance efforts can be simplified by minimizing the effects of change, enabling classes with dissimilar interfaces to communicate with each other through an intervening component capable of converting the interface a caller uses into the one a receiver expects, rendering their interface incompatibilities moot. We can choose whether it is the class scope or object scope variation of the Adapter design pattern that best suits our requirements.

Adapter Exercises

Refer to Chapter 12 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 305 series: ZOOT305A and ZOOT305B.

Footnotes

1 This set of shapes is based on those used with the explanation of the Adapter pattern found in the book Head First Design Patterns by Eric Freeman, et al. (O’Reilly, 2004).

2 GoF, p. 139.

3 GoF, p. 141.

4 According to GoF, the class Adapter facilitates multiple inheritance (GoF, p. 141). This UML class diagram for the adapter component shows only the use of single inheritance in combination with an interface.

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

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