Polymorphism is one of the most powerful advantages of object-oriented programming. Multiple subclasses that inherit from the same super class can override behavior in the super class by providing unique implementations for the methods defined in the super class. This allows each subclass to express a different behavior in response to the same method call on the super class. In effect, polymorphism allows the invocation of a given method on an object reference to a super class at compile time that is dispatched to the overriding method in the actual instantiated subclass at run time.
Thus, a super class defines a given method and a derived class overrides the method with its own behavior. As described previously, overrides of this method can be defined in additional subclasses of the initial overriding derived class to any depth in the class hierarchy of an object. However, the effective method override for any given class hierarchy is the override defined by the most derived subclass in the hierarchy.
To polymorphically invoke an overridden method on an object reference:
1. Instantiate a class whose hierarchy overrides the method defined in one of its super classes.
2. Obtain an object reference to the instantiated class whose class type is the super class that defines the method. Some ways of doing this include:
Passing the object reference to the instantiated class as an argument to a method whose corresponding parameter is defined as the appropriate super class type.
Assigning the result of the NEW function that instantiates the class to an object reference data element of the appropriate super class type.
3. Invoke the method on this super class object reference.
The actual method that executes is the override defined in the most derived subclass within the object’s class hierarchy.
The following figure shows how a method can be called polymorphically on the defining super class within a class hierarchy, using pseudo-code to represent the behavior. The figure shows three classes forming an inheritance hierarchy with ClassA as the root class and ClassC as the most derived subclass. ClassA defines a MethodA( ), which is overridden by ClassB, which is also the most derived subclass that overrides the method.
The pseudo-code fragment defines an object reference (dotted arrow) to the ClassA data type (ObjectA). As shown by the numbered arrows, the code then instantiates ClassC (1a), setting ObjectA to reference the instance, calls MethodA( ) on ObjectA. The method that executes is the override defined in the most derived overriding subclass, ClassB (1b).
Figure 11. Invoking a method polymorphically (one subclass)
The following figure shows another polymorphic method call. In this case, the object is instantiated from ClassD (2a), which extends the same class hierarchy and also overrides MethodA( ). So, the call to MethodA( ) on ObjectA executes the override defined in the most derived overriding subclass, ClassD (2b).
Figure 12. Invoking a method polymorphically (another subclass)
The following ABL classes show a practical example of polymorphism, where the class instances that define method overrides are passed to a common application method (displayArea( )) that, in turn, calls an overridden method (calculateArea( )) on a ShapeClass super class that originally defines the method.
In this example, ShapeClass is extended by both CircleClass and RectangleClass. The super class ShapeClass defines the method calculateArea( ), as shown:
CLASS ShapeClass:
METHOD PUBLIC DECIMAL calculateArea ( ): /* Dummy routine */ MESSAGE "If you got here someone did not override this method!" VIEW-AS ALERT-BOX. END METHOD.
END CLASS.
Both RectangleClass and CircleClass also define a calculateArea( ) method. The RectangleClass uses the length and width, as shown:
CLASS RectangleClass INHERITS ShapeClass:
DEFINE PRIVATE VARIABLE length AS DECIMAL NO-UNDO.
DEFINE PRIVATE VARIABLE width AS DECIMAL NO-UNDO.
CONSTRUCTOR PUBLIC RectangleClass
(INPUT l AS DECIMAL, INPUT w AS DECIMAL):
ASSIGN
length = l
width = w.
END CONSTRUCTOR.
METHOD PUBLIC OVERRIDE DECIMAL calculateArea ( ): RETURN length * width. END METHOD.
END CLASS.
The CircleClass needs the radius only, as shown:
CLASS CircleClass INHERITS ShapeClass:
DEFINE PRIVATE VARIABLE pi AS DECIMAL NO-UNDO INITIAL 3.14159.
DEFINE PRIVATE VARIABLE radius AS DECIMAL NO-UNDO.
CONSTRUCTOR PUBLIC ShapeClass (INPUT r AS DECIMAL):
radius = r.
END CONSTRUCTOR.
METHOD PUBLIC OVERRIDE DECIMAL calculateArea ( ): RETURN pi * radius * radius. END METHOD.
END CLASS.
The following Main class demonstrates the polymorphic behavior of these classes:
CLASS Main:
DEFINE VARIABLE rRectangle AS CLASS RectangleClass NO-UNDO. DEFINE VARIABLE rCircle AS CLASS CircleClass NO-UNDO. DEFINE VARIABLE width AS DECIMAL NO-UNDO INITIAL 10.0.
DEFINE VARIABLE length AS DECIMAL NO-UNDO INITIAL 5.0.
DEFINE VARIABLE radius AS DECIMAL NO-UNDO INITIAL 100.0.
CONSTRUCTOR PUBLIC Main ( ):
rRectangle = NEW RectangleClass(width, length). rCircle = NEW CircleClass (radius). displayArea( rRectangle ).
displayArea( rCircle ).
END CONSTRUCTOR.
METHOD PUBLIC VOID displayArea( INPUT rShape AS CLASS ShapeClass ):
MESSAGE rShape:calculateArea( ) VIEW-AS ALERT-BOX.
END METHOD.
END CLASS.
Note that the first two definitions define rRectangle and rCircle variables as references to the RectangleClass and CircleClass, respectively. When each type of ShapeClass instance is created, the specific shape type is specified in the NEW function to specialize the general ShapeClass type.
To operate on these RectangleClass and CircleClass instances polymorphically, the displayArea( ) method specifies the general ShapeClass type as an INPUT parameter. This parameter can accept an object reference to any subclass of ShapeClass. This means that while only methods defined in ShapeClass can be invoked with this object reference, the implementation that is executed depends on the actual subclass instance that the ShapeClass object reference is pointing to. In this case, displayArea( ) invokes the ShapeClass method, calculateArea( ), in order to display the area of the particular ShapeClass object that is input, according to its instantiated class type.
The constructor for Main, then, instantiates an instance of RectangleClass and CircleClass and passes each one, in turn, to displayArea( ). Because of the polymorphic relationship to these specialized subclass instances of ShapeClass, the version of calculateArea( ) that is run is the version in each RectangleClass and CircleClass instance, respectively.
So, the use of polymorphism depends on the principle that a general domain exists over which a set of common operations can be applied and tailored to suit the requirements of more specialized subsets of that domain. In the previous example, the general domain is the domain of shapes and each specialized domain consists of different types of shapes, such as rectangles and circles. The subclass for each of these specialized shapes implements the same common set of operations to suit the requirements of its own specialized domain subset.
In the larger domain of business applications, one can similarly imagine many different uses for polymorphism. For example, in a general ledger system, you might have a super class for the domain of general accounts that provides a set of methods that operate on all accounts. You might then have one subclass representing asset accounts and another subclass representing liability accounts that both inherit from the general accounts class. In a general ledger system, some of the same methods inherited from the general accounts class would function as inverses of each other as implemented in the liability accounts class or the asset accounts class.
You might then create even more specialized subclasses that inherit from the asset accounts or liability accounts class. So, for example, in a hospital system, you might create a subclass of employee accounts that inherits from the liability accounts class and a subclass of patient accounts that inherits from the asset accounts class. Of course, there are many additional accounts that might be represented as subclasses of either the liability or asset accounts class in such a system.
Thus, by understanding the domain of a business environment and the specific problem that an application is intended to solve, you can virtually always represent the solution in terms of these polymorphic relationships between more general and more specialized domains. And you can likely implement any of the problem solutions for these domains using classes in ABL.