Client/Supplier Adaptation Technique |
[Note that the examples used below to illustrate this adaptation technique may be out of date and not relevant anymore for recent versions of the supported Eiffel compilers.]
Although it is the most object-oriented way to adapt existing classes, the inheritance mechanism sometimes fails to fulfill its job when dealing with kernel classes. This is typically the case for basic types such as INTEGER or CHARACTER, or optimized classes such as ARRAY or STRING. To get around this problem, another technique using client/supplier relationship can be adopted.
This technique, which admittedly doesn't look nice in a pure object-oriented environment, consists in writing features which need to be adapted in a client class instead of in a descendant class. For example in ELKS '95 there is no way to get a CHARACTER knowing its ASCII code. What is needed is an extra routine in class INTEGER:
to_character: CHARACTER -- Character whose ASCII code is Current require large_enough: Current >= Minimum_character_code small_enough: Current <= Maximum_character_code ensure valid_character_code: Result.code = Current
Some Eiffel compilers already support this routine in class INTEGER. The other compilers provide other means to get the same result. But class INTEGER is half built-in in most compilers, so adapting this class through inheritance would result in run-time misbehaviors. The solution adopted here is to introduce a facility class KL_INTEGER_ROUTINES containing the following routine:
to_character (an_integer: INTEGER): CHARACTER -- Character whose ASCII code is `an_integer' require an_integer_large_enough: an_integer >= Minimum_character_code an_integer_small_enough: an_integer <= Maximum_character_code ensure valid_character_code: Result.code = an_integer
and other such adapted routines to be applied to INTEGERs. As with the inheritance technique, there will be a different cluster for each supported Eiffel compiler, and each such cluster will have the same set of classes. These classes are facility classes, such as KL_INTEGER_ROUTINES, associated with a given kernel class. They contain a set of routines whose first argument is the object that routine should be applied to.
There are two typical ways to use the classes described above. They can be used as mixin classes. This means that a given class will inherit from one of these classes to have access to its features, as shown below:
class FOO inherit KL_INTEGER_ROUTINES export {NONE} all end feature -- Basic operations do_something is -- Do something with a character obtained from -- its ASCII code provided as input by the user. local c: CHARACTER do io.read_integer c := to_character (io.last_integer) ... end end
(Note that all features of KL_INTEGER_ROUTINES have been made secret since this class is inherited for implementation only.) The disadvantage of this method is that it rapidly pollutes the name space in the application classes. A possible way to handle this problem would be to declare facility classes, such as KL_INTEGER_ROUTINES, as expanded and use them in local variables:
expanded class KL_INTEGER_ROUTINES feature ... end
class FOO feature -- Basic operations do_something -- Do something with a character obtained from -- its ASCII code provided as input by the user. local integer_routines: KL_INTEGER_ROUTINES c: CHARACTER do io.read_integer c := integer_routines.to_character (io.last_integer) ... end end
Some Eiffel compilers will even optimized the code above by inlining the call to to_character and hence avoiding the (implicit) creation of the expanded object. Unfortunately, user-defined expanded types are not properly supported by all Eiffel compilers, making it impossible to use this mechanism in portable code. To work around this problem and to avoid having to create an instance of these _ROUTINES classes "by hand" each time they are used, they can be accessed through once functions. For example, for class KL_INTEGER_ROUTINES, the following class will be provided:
class KL_IMPORTED_INTEGER_ROUTINES feature -- Access INTEGER_: KL_INTEGER_ROUTINES -- Routines that ought to be in class INTEGER once create Result ensure integer_routines_not_void: Result /= Void end end
and class FOO will become:
class FOO inherit KL_IMPORTED_INTEGER_ROUTINES feature -- Basic operations do_something -- Do something with a character obtained from -- its ASCII code provided as input by the user. local c: CHARACTER do io.read_integer c := INTEGER_.to_character (io.last_integer) ... end end
The class name convention _IMPORTED_ has been adopted after reading an article from Richie Bielak which appeared in Eiffel Outlook in May 1994 (volume 3, number 5, page 6). The use of INTEGER_, all characters in uppercase (instead of the typical style guideline which states that once function names should have their first letter capitalized and all remaining in lowercase), has been chosen to make it clear that these routines really ought to be implemented in class INTEGER itself. The underscore at the end is to avoid compilation problems with some Eiffel compilers which could consider, with no good reasons in my opinion, INTEGER as one of Eiffel's reserved words.
The technique described above is not what we can call a masterpiece of object-oriented programming, but it has the advantage of getting around the drawbacks of the other technique using adaptation by inheritance. However the two techniques presented so far only take care of class and feature incompatibilities, but for the sake of interoperability, they will fail to deal with language differences (also known as dialects) due to bugs or extensions provided by some Eiffel compilers. The use of a simple preprocessor will help in such situations.
Copyright © 1998-2016, Eric
Bezault maito:ericb@gobosoft.com http://www.gobosoft.com Last Updated: 23 December 2016 |