Object-oriented design provides multiple models for facilitating code reuse. One model is inheritance, which is the model shown in most of the examples thus far. The other code reuse model is the delegation model. This section demonstrates how you can use the delegation model in ABL. In the delegation model one class acts as a principal object for a set of related behavior that is implemented by other classes. This principal object is called a container class. The container class can create instances of other classes with the NEW function. These other classes that provide services to the container are called delegate classes. The container class executes delegate class behavior by forwarding method invocations (object messages) on to the delegate for processing. In this way, the principal object acts as a container for aggregated behavior in the sense that it is responsible for starting, managing, and using the other classes that provide services to the container, which in turn provides the services of all its delegates to users or clients of the container. In addition, each delegate class can be used by multiple containers to provide the same services for different purposes.
The association between a container class and a delegate class is somewhat looser than the strict compile-time definition of a class hierarchy, in which each class in the hierarchy explicitly states its position within the hierarchy as its first statement. There is nothing inherent in a class, no statement in a class, that specifically defines it as a container or a delegate. However, note that because of strong typing, the relationships and dependencies between a container and its delegates are verified and enforced at compile-time; the compiler references the code for each delegate of a container in order to validate all method calls to the delegate from the container. Any variable that holds an object reference must be defined explicitly for the object’s class type. Therefore, the compiler knows exactly what data members, properties, methods, and events can be accessed through the object reference to a delegate.
To support the running of behavior by procedures and other classes through a container, the container must define a PUBLIC stub method for each method that is implemented by a delegate. In other words, a container class simply using behavior in one of its delegates does not make that delegate behavior available to a container’s client unless the container exposes the behavior through a method of its own.
The following example demonstrates delegation. In the example, only the container is shown. The behavior of the delegates is not represented. The container class and both delegate classes, called logToDB and logToFile, must provide an implementation of the methods openLog( ), writeLog( ), and closeLog( ). You can use two natural mechanisms to enforce this design—an interface or an abstract class.
The following code shows an interface definition for the example:
INTERFACE Ilog:
METHOD PUBLIC VOID openLog (pcName AS CHARACTER).
METHOD PUBLIC VOID writeLog (pcTxt AS CHARACTER).
METHOD PUBLIC VOID closeLog ( ).
END INTERFACE.
The container class itself implements the method prototypes in the Ilog interface. As you can see from the following sample code, the container's version of these methods invokes the same method in one or the other of its two delegates, which are created by the constructor. The container also implements a separate setMode( ) method to specify the delegate behavior to use, which is initially logToFile. For example:
CLASS Container IMPLEMENTS Ilog:
DEFINE PRIVATE VARIABLE rlogToDB AS CLASS logToDB NO-UNDO.
DEFINE PRIVATE VARIABLE rlogToFile AS CLASS logToFile NO-UNDO.
/* logMode = 1 (File) is the default */
DEFINE PRIVATE VARIABLE logMode AS INTEGER INITIAL 1 NO-UNDO.
CONSTRUCTOR PUBLIC Container ( ):
rlogToDB = NEW logToDB ( ).
rlogToFile = NEW logToFile ( ).
END CONSTRUCTOR.
METHOD PUBLIC VOID setMode (INPUT piLogMode AS INTEGER)
logMode = piLogMode.
END METHOD.
METHOD PUBLIC VOID openLog (INPUT pcName AS CHARACTER):
IF logMode EQ 1 THEN
rlogToFile:openLog (pcName).
ELSE
rlogToDB:openLog (pcName).
END METHOD.
METHOD PUBLIC VOID writeLog (INPUT pcTxt AS CHARACTER):
IF logMode EQ 1 THEN
rlogToFile:writeLog (pcTxt).
ELSE
rlogToDB:writeLog (pcTxt).
END METHOD.
METHOD PUBLIC VOID closeLog ( ):
IF logMode EQ 1 THEN
rlogToFile:closeLog ( ).
ELSE
rlogToDB:closeLog ( ).
END METHOD.
END CLASS.
The following code shows an abstract class definition for the example:
CLASS LogAbs ABSTRACT:
METHOD PUBLIC ABSTRACT VOID openLog (pcName AS CHARACTER).
METHOD PUBLIC ABSTRACT VOID writeLog (pcTxt AS CHARACTER).
METHOD PUBLIC ABSTRACT VOID closeLog ( ).
END CLASS.
The container class itself derives from the LogAbs abstract class. As you can see from the following sample code, the container class is almost identical to the version that implements the ILog interface, except that the method implementations specify the OVERRIDE option. For example:
CLASS Container INHERITS LogAbs:
DEFINE PRIVATE VARIABLE rlogToDB AS CLASS logToDB NO-UNDO.
DEFINE PRIVATE VARIABLE rlogToFile AS CLASS logToFile NO-UNDO.
/* logMode = 1 (File) is the default */
DEFINE PRIVATE VARIABLE logMode AS INTEGER INITIAL 1 NO-UNDO.
CONSTRUCTOR PUBLIC Container ( ):
rlogToDB = NEW logToDB ( ).
rlogToFile = NEW logToFile ( ).
END CONSTRUCTOR.
METHOD PUBLIC VOID setMode (INPUT piLogMode AS INTEGER)
logMode = piLogMode.
END METHOD.
METHOD PUBLIC OVERRIDE VOID openLog (INPUT pcName AS CHARACTER):
IF logMode EQ 1 THEN
rlogToFile:openLog (name).
ELSE
rlogToDB:openLog (name).
END METHOD.
METHOD PUBLIC OVERRIDE VOID writeLog (INPUT pcTxt AS CHARACTER):
IF logMode EQ 1 THEN
rlogToFile:writeLog (pcTxt).
ELSE
rlogToDB:writeLog (pcTxt).
END METHOD.
METHOD PUBLIC OVERRIDE VOID closeLog ( ):
IF logMode EQ 1 THEN
rlogToFile:closeLog ( ).
ELSE
rlogToDB:closeLog ( ).
END METHOD.