Client/Supplier Adaptation Technique | ![]() ![]() |
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 is 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 do_something is -- ... local c: CHARACTER do io.read_integer c := to_character (io.last_integer) ... end end -- class FOO
(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 pollute the name space in the application classes. As a consequence, facility classes are also often used as expanded in local entities:
class FOO feature do_something is -- ... local integer_routines: expanded KL_INTEGER_ROUTINES c: CHARACTER do io.read_integer c := integer_routines.to_character (io.last_integer) ... end end -- class FOO
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, 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 INTEGER_ is -- Routines that ought to be in class INTEGER once !! Result ensure integer_routines_not_void: Result /= Void end end -- class KL_IMPORTED_INTEGER_ROUTINES
and class FOO will become:
class FOO inherit KL_IMPORTED_INTEGER_ROUTINES feature do_something is -- ... local c: CHARACTER do io.read_integer c := INTEGER_.to_character (io.last_integer) ... end end -- class FOO
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 since INTEGER is, with no good reasons in my opinion, one of Eiffel's reserved words. (Note that if you are using SmallEiffel, you will have to specify the command line option -no_style_warning if you don't want to be overwhelmed by hundreds of warnings telling you that INTEGER_ and the like are in uppercase. I still don't understand why SmallEiffel generates these warnings since the corresponding code is correct Eiffel!)
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, Eric
Bezault maito:ericb@gobosoft.com http://www.gobosoft.com Last Updated: 10 August 1998 |
![]() ![]() ![]() ![]() |