Next Previous Up Top


4 Class Relationships

4.5 Choosing Between Inheritance and Association


4.5 Choosing Between Inheritance and Association


Looking at the IntQueue and IntDequeue in the section above, it can be seen that a IntQueue is simpler than an IntDequeue; an IntDequeue is more specialized than an IntQueue. This is what led us to take the decision that IntDequeue could be a subclass of IntQueue -- we saw that we could make use of already written code. Perhaps more importantly though, an IntDequeue can do everything that an IntQueue can do, allowing an IntDequeue object to be substituted for an IntQueue object without changing the behaviour of a program. An IntDequeue may do more than an IntQueue but, nonetheless, it can function without problem as an IntQueue: an IntDequeue 'is-a' IntQueue. Thus, we can begin to see that inheritance actually serves two distinct purposes:

The sharing of implementation using inheritance seems to be a relatively straightforward issue. If two or more classes need to use the same variables or methods, a superclass can be created to contain them and inheritance used to access them. The advantage of doing this is that the variable and method declarations no longer need to be duplicated and only need to be written and tested once.

However, is inheritance always the best way to share code? An alternative design approach is to use association and to put the shared methods and variables into another class but gain access via an instance variable referring to an object of that class. In terms of code, the alternatives are:

Inheritance


class X
{
	public void f() { ... }
	protected int a ;
}

class Y extends X
{
	...
}

Association


class X 
{
	public void f() { ... }
	public int a ;
}

class Y
{
	public void f() 
	{
		x.f();
	}
	private X x = new X ;
}

How do we decide which approach to take in a given situation? The answer comes if we think carefully about why we are relating the classes and about the public interfaces needed by the classes involved. If a class simply needs to use the implementation of another class but does not need to have (and extend) the same public interface then inheritance is not required and association can, indeed should, be used. In fact, in this situation, trying to use inheritance is usually a mistake, not least because the new subclass will end up with an inherited interface it doesn't need.

If a decision is made to use inheritance, it should be the case that both the implementation and the public interface of the proposed superclass are needed. It should be possible to substitute a subclass object for a superclass object and still expect a program to run correctly. A good rule-of-thumb when checking that inheritance is appropriate is to try and verbalize the relationship between the two classes. If you can say that a class Y is-a or is-a-kind-of class X and it makes sense, then inheritance is probably appropriate. For example, it makes sense to say that an IntDequeue is a kind of IntQueue but it doesn't make sense to say that an IntDequeue is a kind of Stack.

In passing, we need to note that using association for sharing is not without its drawbacks. Looking at the association version of class X above, notice that both the method and variable have been made public. This needs to be done so that class Y can gain access to the variable a. So the drawback is that more of class X has to be made public, weakening the encapsulation and increasing the risk of misuse. Java supports strong encapsulation to reduce such risks but only if the programmer uses it effectively.

A better way of implementing class X would be to provide accessor functions to allow access to the member variable, which can then remain private:

class X 
{
	public void f() ;
	public void seta(int) ; 							// Setter function to set a
	public int geta() ;							// Getter function to get value of a

	private int a ;							// Now safely private
}

A setter function, seta, is used to set the value of a, while a getter function, geta, is used to retrieve the value of a. At first sight this appears simply to add the inconvenience of having to call methods in order to use the variable a, meaning that operations such as direct assignment to a are no longer possible. However, it has had the affect of decoupling the details of the variable and its type from users of class X. If in the future it was decided to change the way in which the value a was stored (say by using a variable of a different type or even a value computed on demand rather than stored directly), the external interface of the class could remain the same and users of the class would be unaffected regardless of the internal, but private, changes.

The principle being exploited here is: Decouple classes as much as possible. This means always thinking ahead about how the implementation of a class may change and avoid any of those changes having to be propagated to all the users of the class. Ideally, once the public interface (i.e. the set of public methods) of a class has been defined and publicized, it should at most be changed infrequently and preferably never.


Copyright 1997 Russel Winder and Graham Roberts

Last updated: 6 Oct 1997