8.3 Inheritance
Inheritance is an important concept in OOP that helps us avoid repetition of code and leverage existing code libraries. In Section 8.1, we described how inheritance makes it easy to expand functionality without having to rewrite existing code. With procedural programming techniques, the closest you can get to something similar to inheritance is either editing the existing code to expand the functionality or copying the existing code and then adding the additional functionality. Both approaches are undesirable, because editing existing code may break the original functionality, and it requires a lot of testing to check that the original and the new functionality both work correctly, which can itself be a time-consuming process to go through each time the functionality is enhanced. If we copy the existing code, not only is the code now redundant, but also it leads to a maintenance nightmare, because each update to the original code needs to be replicated in all of the copies.
Inheritance allows you to expand a class to meet more specific requirements while reusing what’s already been developed without having to repeat the existing code. For example, say that during the initial stage of development you defined a class to process information about students. Because your requirement didn’t differentiate between types of students, you developed a generic student class.
However, later in the development process, the requirement has been enhanced to branch students into various categories—commerce students, IT students, science students, and so on—with each student category required to perform specific tasks. As the requirement becomes more specific, you can simply inherit the original student class to add more specific functionality for each student category, as shown in Listing 8.17. This allows you to move from generic functionality to specific functionality without breaking the existing implementation while reusing the effort that already went into the original development.
CLASS cl_student DEFINITION.
PUBLIC SECTION.
METHODS tuition_fee.
ENDCLASS.
CLASS cl_commerce_student DEFINITION INHERITING FROM cl_student.
PUBLIC SECTION.
METHODS tuition_fee REDEFINITION.
METHODS subject. "New method for additional functionality
ENDCLASS.
CLASS cl_commerce_student IMPLEMENTATION.
METHOD tuition_fee.
"Logic to calculate tuition fee for commerce students goes here
ENDMETHOD.
METHOD subject.
ENDMETHOD.
ENDCLASS.
In Listing 8.17, the new cl_commerce_student class is a subclass of the original student superclass from which it’s inheriting, cl_student.
In this section, we’ll look at the different components of the inheritance concept. We’ll then look at both abstract and final classes and methods before moving on to composition relationships. In the final subsection, we’ll look at how to use the refactoring assistant in Class Builder to move class components in the inheritance tree.
8.3.1 Inheriting Components
In Listing 8.16, we were only concerned with what can be accessed from within and outside of a class. The public section allowed access from external applications, while the private section restricted access to components within the class.
Inheritance brings a new dimension to implementation hiding. With the protected section, you can restrict access to the components from external applications but allow access to the subclasses. Therefore, for external programs, the components defined in the protected section are similar to the components in the private section. This allows you to provide access to child classes without exposing the components of a parent class to the public.
Listing 8.18 provides some sample code to demonstrate inheritance. In this example, we’re demonstrating how all the components of the parent class are inherited by the child class.
CLASS cl_parent DEFINITION.
PUBLIC SECTION.
METHODS set_value IMPORTING value TYPE string.
PROTECTED SECTION.
DATA value TYPE string.
METHODS check_value.
PRIVATE SECTION.
METHODS reset_value.
ENDCLASS.
CLASS cl_parent IMPLEMENTATION.
METHOD set_value.
ENDMETHOD.
METHOD check_value.
ENDMETHOD.
METHOD reset_value.
ENDMETHOD.
ENDCLASS.
CLASS cl_child DEFINITION INHERITING FROM cl_parent.
ENDCLASS.
CLASS cl_child IMPLEMENTATION.
ENDCLASS.
DATA child TYPE REF TO cl_child.
START-OF-SELECTION.
CREATE OBJECT child.
Child->set_value( value = 'child' ).
In Listing 8.18, the cl_child class is defined as a subclass of cl_parent. Here, cl_parent is the superclass, and it implements the set_value method in the public section, the check_value method and the value attribute in the protected section, and the reset_value method in the private section.
The cl_child class is inherited from cl_parent using the inheriting from addition of the class definition statement. This sets the parent-child relationship between the two classes. The cl_child class doesn’t list any components on its own. All the components in the public and protected sections of the cl_parent superclass are, by default, available to the cl_child subclass. This is shown in the program code under start-of-selection (see Listing 8.18), where a reference object child is defined by referring to cl_child and the set_value method of cl_parent is accessed using this child class reference with the statement Child->set_value( value = 'child' ).
Keep the following important points about inheritance in mind:
- The subclass can access the components defined under public and protected visibility sections in the superclass.
- You can’t define a component in a subclass with the same name as the component in the public or protected sections of the superclass.
- Because the private section of the superclass is invisible to the subclass, you can define a component in the subclass with the same name as the component in the superclass.
- The visible instance attributes in the superclass can be accessed from the subclass directly or by using the self-reference variable me.
- The visible instance methods of the superclass can be accessed from the subclass using the pseudo-reference variable super.
- The static attributes of a class are associated with the complete inheritance tree (all the classes in the hierarchy) and not just with a single class, so they can be accessed using any class reference in the hierarchy. For example, if three classes A, B, and C are in inheritance relationship (B inherits A and C inherits B) and class A contains a static attribute counter, then this attribute can be accessed as A=>counter or B=>counter or C=>counter.
- The constructor of the superclass is not inherited by the subclass. This allows the subclass to define its own constructor, but to ensure the components of the superclass are instantiated properly, it’s mandatory to call the constructor of the superclass in the constructor of the subclass, for instance constructors. If the subclass implements a static constructor, the runtime environment automatically calls the static constructor of the superclass when the subclass is instantiated if the static constructor of the superclass isn’t yet called.
- The constructor of the class will have visibility to its own components. For example, if a method is redefined in the subclass and this method is accessed in the constructor of both the superclass and subclass, then the constructor of the subclass will execute the redefined method in the subclass, while the constructor of the superclass will execute the original method in the superclass, as shown in Listing 8.19.
- A method of the superclass can be redefined in the subclass using the REDEFINITION addition to the METHODS statement in the definition part of the subclass, as shown in Listing 8.19. This allows you to enhance the functionality of the superclass method in the subclass.
- Any changes in the superclass will be automatically available to the subclass, whereas changes to subclasses don’t affect the superclass. For example, if a method of the superclass is enhanced, then the enhancements will be automatically available to the subclass. However, if the same method is changed (redefined) in the subclass, then it won’t impact the superclass.
- The definition of a component of superclass can’t be changed in the subclass; in other words, you can redefine a superclass method in a subclass, but you can’t change its parameter interface.
CLASS cl_parent DEFINITION.
PUBLIC SECTION.
METHODS constructor.
PROTECTED SECTION.
METHODS meth.
ENDCLASS.
CLASS cl_parent IMPLEMENTATION.
METHOD constructor.
Me->meth( ).
ENDMETHOD.
METHOD meth.
WRITE 'I'm in parent class'.
ENDMETHOD.
ENDCLASS.
CLASS cl_child DEFINITION INHERITING FROM cl_parent.
PUBLIC SECTION.
METHODS constructor.
PROTECTED SECTION.
METHODS meth REDEFINITION.
ENDCLASS.
CLASS cl_child IMPLEMENTATION.
METHOD constructor.
super->constructor( ).
Me->meth( ).
ENDMETHOD.
METHOD meth.
WRITE 'I'm in child class'.
ENDMETHOD.
ENDCLASS.
DATA child TYPE REF TO cl_child.
START-OF-SELECTION.
CREATE OBJECT child.
In Listing 8.19, cl_parent is defined with an instance constructor and the meth method. cl_child inherits the cl_parent class, and the meth method is redefined in the cl_child subclass. A reference object, child, is defined in the program referring to cl_child. When the reference object child is instantiated under start-of-selection using the CREATE OBJECT statement, it will call the constructor of the cl_child class. However, because it’s mandatory to call the parent constructor before any instance components can be accessed, the constructor in cl_child calls the parent constructor using the super->constructor( ) statement.
The meth method is called in the respective constructors of the superclass and subclass. The constructor of the cl_child subclass calls the redefined meth method in cl_child, whereas the constructor of the cl_parent superclass calls the meth method defined in cl_parent. You can keep a breakpoint on the CREATE OBJECT statement and execute the code to see the sequence of the code execution in debug mode for easy understanding.
For global classes in Class Builder, the inheritance relation can be maintained either while creating the class, as shown in Figure 8.3, or under the Properties tab, as shown in Figure 8.4.
To redefine a method in the subclass, you can use the Redefine button in Class Builder, as shown in Figure 8.5.
8.3.2 Abstract Classes and Methods
Sometimes, you may want to define a generic class that can be used as a template that can be implemented in subclasses. Defining a dummy superclass with dummy methods may not be a good way of defining this template. For example, if you have students and various course categories and each student course category has a different process to determine its tuition, then you can define a student class with a tuition_fee method that can be used as a template, and its implementation can be maintained more specifically in the specific subclasses that inherit this class. For example, a commerce_student class can inherit the student class and implement the tuition_fee method specific to commerce students.
If you define this student class as a regular class, then you also need to maintain its implementation. However, because this is a generic class, maintaining the implementation doesn’t make sense. In such scenarios, we can define this student class as an abstract class and define the tuition_fee method as an abstract method.
An abstract class only maintains a definition; no implementation is required for such a class if it contains all abstract components, which are inherited in a subclass in which the specific implementation can be maintained. Listing 8.20 provides an example of using an abstract class.
CLASS cl_student DEFINITION ABSTRACT.
PUBLIC SECTION.
METHODS tuition_fee ABSTRACT.
ENDCLASS.
CLASS cl_commerce_student DEFINITION INHERITING FROM cl_student.
PUBLIC SECTION.
METHODS tuition_fee REDEFINITION.
ENDCLASS.
CLASS cl_commerce_student IMPLEMENTATION.
METHOD tuition_fee.
"Logic to calculate tuition fee for commerce students goes here
ENDMETHOD.
ENDCLASS.
CLASS cl_science_student DEFINITION INHERITING FROM cl_student.
PUBLIC SECTION.
METHODS tuition_fee REDEFINITION.
ENDCLASS.
CLASS cl_science_student IMPLEMENTATION.
METHOD tuition_fee.
"Logic to calculate tuition fee for science students goes here
ENDMETHOD.
ENDCLASS.
In Listing 8.20, cl_student is defined as an abstract class by using the ABSTRACT addition with the CLASS DEFINITION statement.
An abstract method is defined in class cl_student using the ABSTRACT addition with the METHODS statement. Because this class contains an abstract method, it can only be implemented in a subclass by using the redefinition addition. If the class contains a regular method (not an abstract method), then we need to maintain the implementation of that method in the implementation part of the class cl_student. This implementation then would be part of the template that the subclasses inherit. Because there are no regular methods in Listing 8.20, we’ve ignored the implementation of the cl_student class completely.
The code in Listing 8.20 defines two subclasses—cl_commerce_student and cl_science_student—which are inherited from the cl_student abstract class. The tuition_fee method is redefined in the respective subclasses to implement specific functionality.
Note
It isn’t possible to create an instance of an abstract class, because an abstract class is only used as a template.
For global classes, the abstract property for a class is set under the Properties tab in the Inst.Generation field, as shown in Figure 8.6.
For methods in Class Builder, the abstract property is set by keeping the cursor on the method, selecting the Detail View button, and then selecting the Abstract checkbox, as shown in Figure 8.7.
8.3.3 Final Classes and Methods
Sometimes, in the inheritance tree, it may not make sense to expand a class further. In such a scenario, you can make the class final, which means it can’t be inherited further. A local class can be defined as a final class by adding FINAL to the CLASS DEFINITION statement, as shown in Listing 8.21.
CLASS cl_final DEFINITION FINAL.
……
ENDCLASS.
For global classes, to define a class as final in Class Builder, the Final checkbox needs to be selected in the Properties tab, as shown in Figure 8.8.
Methods can also be defined as final so that they can’t be redefined further in the subclass. For example, it may make sense to further inherit a class, but a method in the class may not need to be extended further because it’s purpose is complete. You can make a method final by adding FINAL to the METHODS statement, as shown in Listing 8.22.
CLASS cl_parent DEFINITION.
PUBLIC SECTION.
METHODS student FINAL.
ENDCLASS.
To make the method of a global class final, keep the cursor on the method in the Class Builder and select the Detail View button. In the Change Method window, select the Final checkbox, as shown in Figure 8.9.
8.3.4 Composition
Using inheritance, we design classes that fit an is a relationship. For example, a commerce student is a type of student, so we define the class commerce_student as a subclass of student in the example in Listing 8.20. Sometimes, in an effort to reuse the existing code, developers create an inheritance tree that doesn’t fit an is a relationship. For example, if we have an existing orders class, it makes sense to create a sales_order class inheriting from the orders class, because a sales order is an order.
However, if you want to define a class for delivery, it may not make sense to inherit the orders class, because a delivery is not an order. However, each order has one or more deliveries associated with it. If the objects fit the has a relationship, we call it a composition relationship.
Composition allows you to reuse existing functionality by maintaining the existing object as an attribute in the class. Therefore, the orders class can have delivery as an attribute in the class. This arrangement allows you to take advantage of the existing functionality in the system, as shown in Listing 8.23.
CLASS cl_delivery DEFINITION.
PUBLIC SECTION.
METHODS get_delivery.
ENDCLASS.
CLASS cl_delivery IMPLEMENTATION.
METHOD get_delivery.
ENDMETHOD.
ENDCLASS.
CLASS cl_orders DEFINITION.
PUBLIC SECTION.
METHODS track_order.
PRIVATE SECTION.
DATA delivery TYPE REF TO cl_delivery.
ENDCLASS.
CLASS cl_orders IMPLEMENTATION.
METHOD track_order.
CREATE OBJECT delivery.
delivery->get_delivery( ).
ENDMETHOD.
ENDCLASS.
In Listing 8.23, the cl_delivery class is maintained as an attribute in the cl_orders class. The delivery object is instantiated in the track method to access the delivery information about the order.
Tip
As a rule of thumb, use an inheritance relationship between objects if they fit the is a relationship and use composition if the objects fit the has a relationship. This tip should help you make better design decisions.
8.3.5 Refactoring Assistant
When you’re designing an application, sometimes you may miss defining the class components at the right level in the inheritance hierarchy. For example, you may have defined a method in the subclass that may make more sense in the superclass due to a change in the requirement.
In such a scenario, you can use the refactoring assistant tool in Class Builder to move the class components up or down the inheritance tree. This saves a lot of effort and any chance of missing steps when manually moving the components.
To use the refactoring assistant, select the menu path Utilities • Refactoring • Refactoring in Class Builder, as shown in Figure 8.10.
In the Refactoring Assistant window (see Figure 8.11), you can drag and drop components to the right hierarchy level.
Now that we’ve walked through the inheritance principles, let’s move on to using polymorphism in OOP.