With facility inheritance we are even less coy than with implementation inheritance about why we want the marriage: pure, greedy self-interest. We see a class with advantageous features and we want to use them. But there is nothing to be ashamed of: the class has no other raison d'être.
The Base Library includes a class ASCII:
indexing description: "The ASCII character set. % %This class may be used as ancestor by classes needing its facilities."; class ASCII feature -- Access Character_set_size: INTEGER is 128; Last_ascii: INTEGER is 127; First_printable: INTEGER is 32; Last_printable: INTEGER is 126; Letter_layout: INTEGER is 70; Case_diff: INTEGER is 32; -- Lower_a -- Upper_a ... Ctrl_a: INTEGER is 1; Soh: INTEGER is 1; Ctrl_b: INTEGER is 2; Stx: INTEGER is 2; ... Blank: INTEGER is 32; Sp: INTEGER is 32; Exclamation: INTEGER is 33; Doublequote: INTEGER is 34; ... Exclamation: INTEGER is 33; Doublequote: INTEGER is 34; ... Upper_a: INTEGER is 65; Upper_b: INTEGER is 66; ... Lower_a: INTEGER is 97; Lower_b: INTEGER is 98; ... etc. ... end -- class ASCII
This class is a repertoire of constant attributes (142 features in all) describing properties of the ASCII character set. As the description entry states, it is meant to be inherited by classes needing access to such properties.
Consider for example a lexical analyzer --- the part of a language analysis system that is responsible for identifying the basic elements, or tokens, of an input text; these tokens may be (assuming the input is a text in some programming language) integer constants, identifiers, symbols and so on. One of the classes of the system, say TOKENIZER, will need access to the character codes, so as to classify the input characters into digits, letters etc. Such a class will inherit these codes from ASCII:
class TOKENIZER inherit ASCII feature ... Routines here may use such features as Blank, Case_diff etc. ... end
Classes such as ASCII have been known to raise a few eyebrows; before going into the methodological discussion of whether they are a proper application of inheritance, we will look at another example of facility inheritance.
The second example will show a case in which the inherited features are not just constant attributes (as with ASCII) but routines of the most general kind.
Assume that we want to provide a general mechanism to iterate over data structures of a certain kind, for example linear structures such as lists. "Iterating" means performing a certain procedure, say action, on elements of such a structure, taken in their sequential order. A number of iteration mechanisms must be provided, including: applying action to all the elements; applying it to all the elements that satisfy a certain criterion given by a boolean-valued function test; applying it to all the elements up to the first one that satisfies test, or the first one that does not satisfy this condition; and so on. A system that uses the mechanism must be able to apply it to any action and test of its choice.
At first it might seem that the iterating features should belong to the data structure classes themselves, such as LIST or SEQUENCE; but as an exercise invites you to determine for yourself this is not the right solution. It is preferable to introduce a separate hierarchy for iterators:
Class LINEAR_ITERATOR, the one of interest for this discussion, looks like this:
indexing description: "Objects that are able to iterate over linear structures"; status: "See notice at end of class"; names: iterators, iteration, linear_iterators, linear_iteration; date: "$Date: 2007-03-30 11:10:11 -0800 (Fri, 30 Mar 2007) $"; revision: "$Revision: 1.7 $" deferred class LINEAR_ITERATOR [G] inherit ITERATOR [G] redefine target end; feature -- Cursor movement target: LINEAR [G]; -- The structure to which iteration features will apply. test: BOOLEAN is -- The boolean condition used to select applicable elements deferred end; action is -- The action to be applied to selected elements deferred end; do_if is -- Apply action in sequence to every item of target that satisfies test. do from start invariant invariant_value until exhausted loop if test then action end; forth end ensure then exhausted end; ... And so on: do_all, do_while, do_until etc. ... end -- class LINEAR_ITERATOR
Now assume a class that needs to perform a certain operation on selected elements of a list of some specific type; for example a command class in a text processing system may need to justify all paragraphs in a document, excepted for preformated paragraphs (such as program texts and other display paragraphs). Then:
class JUSTIFIER inherit LINEAR_ITERATOR [PARAGRAPH] rename action as justify, test as justifiable, do_all as justify_all end feature justify is do ... end; test is -- Is paragraph subject to justification? do Result := not preformated end; ... end -- class JUSTIFIER
The renaming was not indispensable but helps for clarity. Note that there is no need to declare or redeclare the procedure justify_all (the former do_all): as inherited, it does the expected job based on the effected versions of action and test.
Procedure justify, instead of being described in the class, could be inherited from another parent. In this case multiple inheritance would perform a "join" operation that effects the deferred action, inherited from one parent under the name justify (here the renaming is essential), with the effective justify inherited from the other parent. A form of marriage of convenience, in fact.
The two examples, ASCII and LINEAR_ITERATOR, are typical of the two main variants of facility inheritance:
As noted earlier, it is possible to combine both of these variants in a single inheritance link. That is why facility inheritance is one of our categories, not two.
To some people facility inheritance appears to be an abuse of the mechanism --- a form of hacking. It is not. Examination of our examples explains why.
The main question to consider in these examples is not about inheritance but about the classes that have been defined, ASCII and LINEAR_ITERATOR. As always when looking at a class design, we must ask ourselves: "Does this indeed describe a meaningful data abstraction?" --- a set of objects characterized by their abstract properties.
With the examples the answer is less obvious than with a class RECTANGLE, BANK_ACCOUNT or LINKED_LIST, but it exists all the same:
Once these abstractions have been accepted, the inheritance links do not raise any problem: an instance of TOKENIZER does need "access to the properties of the ASCII character set", and an instance of JUSTIFIER does need "the ability to perform sequential iterations on a linear structure". In fact, we could classify such inheritance links examples under the subtype kind. What distinguishes facility inheritance is the nature of the parent.
That the classes themselves are the issue, not the use of inheritance, is reinforced by the observation that an application class could rely on these classes as a client rather than heir. This would make things heavier, especially for ASCII: with
charset: ASCII; ... create charset
every use of a character code would have to be
written charsetlLower_a and the like. The object attached with
ASCII does not play any useful role. With LINEAR_ITERATOR the
same comments apply as long as a given class needs only one kind of iteration.
If several are required, it becomes interesting to create iterator objects,
each with its own version of action and test; then you can have
as many iteration schemes as you need.