to allow reuse without subtyping; to avoid bloated class interfaces; to publicize the interface between a class and its superclass
Not infrequently, inheritance is used just for the sake of
reusing existing implementation. However, in Java inheritance is
linked to subtyping: a subclass not only inherits the member of
its superclass, but also its type is a subtype of that of the
superclass. This lets a
be used as if it were a
although it is not one. Also, inheritance bloats the interface of
Stack with many
even though a
has no use for them. In these cases, it is better to let a
Stack hold a
Vector as delegatee,
to which it can delegate the responsibility for delivering the
services it offers. The following piece of Java code gives an
Stack s =
After the refactoring, the code for
Stack looks as
The code of
Vector and all clients
remain unchanged. The following figure gives an impression of the
before and the after of the refactoring.
After installation, the refactoring is ready for use. Simply open the context menu on a class (not a compilation unit) in the Package Explorer of an Eclipse workspace or in the Outline view of a compilation unit, and select Replace Inheritance With Delegation.
The refactoring then offers you a choice between forwarding and delegation (see below for an explanation) if possible, or names the reasons why one or the other refactoring (or both) cannot be performed (violated preconditions; also see below).
After having made your choice (and as is standard in Eclipse), you can have a preview or have the refactoring performed immediately.
The refactoring is actually more complicated than made believe
by the above simple example. One of the biggest problems is
presence of open recursion (or hook methods; cf. the Template
Method pattern): if a method of the superclass calls
late-bound methods on
removal of inheritance means that methods of the refactored class
can no longer be called this way. This however changes semantics
of the refactored program, which is unacceptable for a
In the context of prototype-based
programming, a clear distinction is made between forwarding
always points to the receiver of the method, while under
always points to the delegator. The following figure illustrates
In prototype-based languages (which do not posses classes), delegation plays the role of inheritance. However, class-based languages like Java support only forwarding between objects; delegation applies only to classes and cannot be utilized in object composition.
There are two ways out of this problem: either refuse to perform the refactoring, or introduce reverse delegation (should be: reverse forwarding), i.e., delegation from the (object of the) (former) superclass to the (object of the) (former) subclass to make the openly recursive calls possible. The former would make absence of open recursion a precondition, the latter either requires changes to the superclass (not desired) or subclassing by a dummy class that serves as delegatee for the class to be refactored. We opt for the latter, but hide the subclass in the inner of the class to be refactored. The exact procedure requires more than sketched here (particularly because of the possible presence of subclasses of the class to be refactored); the following figure only gives a rough impression of the result.
Absence of open recursion is one precondition of replacing inheritance with forwarding; there are several others. We list them here without explanation; some of them are checked using our type inference algorithm, which is also the basis of our Infer Type refactoring and several other tools. Note that Class refers to the class to be refactored, and Superclass to its direct superclass.
supermust be accessible from Class after subclassing is removed.
If the preconditions are satisfied, the refactoring guarantees the following postconditions. Note that Delegator refers to the class to be refactored and Delegatee to the former Superclass or its new subclass (in case of delegation).
super(including those to
superin constructors) are replaced by calls on Delegatee; in case of delegation, those for which reverse delegations exist are replaced by calls to methods forwarding to
superin Delegatee instead (see Postcondition 7).
Objector another superclass of the refactored class (the refactored class may receive a new superclass in certain cases), namely if the constraints for the type of the object being cast cannot be derived. For example, if an instance of
Subis stored in a container typed
Objectwhose source code is unavailable, and if that instance is later retrieved and cast to the type of
Super, a class cast exception will occur once the subtyping of
Superis removed. Of course, this could be prevented by adding a corresponding precondition, but given how many harmless cases this would exclude (and given that unguarded downcasts are a problem anyway), absence of this precondition is a design trade-off rather than a bug.