Java Design:
Building Better Apps and Applets

Chapter 2: Design with Composition, Rather than Inheritance

This document is a mirror page!

This file is a ROUGH conversion of a JAVA DESIGN pre-production file into HTML, using HoTmeTaL Pro.

JAVA DESIGN includes over 170 illustrations. None are included here (HoTMeTaL Pro does not automatically convert .wmf artwork).

So these pages will give you a good idea about what the book is all about, yet is no substitute for the real thing (thank goodness!).

Title: JAVA DESIGN: Building Better Apps and Applets
Authors: Peter Coad and Mark Mayfield
ISBN: 0-13-271149-4

Back to Table of Contents
On to Chapter 3

Design with Composition, Rather than Inheritance

Composition and inheritance are mechanisms for extending a design.

A number of years ago (and perhaps still, in the minds of some designers), inheritance was the only tool for extending responsibilities. And designers used it everywhere.

Yet extending responsibilities with inheritance is applicable only in very specific contexts. In nearly every case, extending the responsibilities with interfaces or with composition is more appropriate.

Use composition to extend responsibilities by delegating work to other objects.

Use inheritance to extend attributes and methods (yet encapsulation is weak within a class hierarchy, so it's a good idea to use this mechanism only when certain criteria are met).

Composition

Composition: what

Composition extends the responsibilities of an object by delegating work to additional objects.

Composition is the major mechanism for extending the responsibilities of an object. Nearly every object in an object model is composed of, knows, or works with other objects (composition).

Composition: an example

Here's the strategy:

And here's an example from Charlie's Charters:

Figure 2-. A passenger object is a composition of reservation objects

This is an example of composition: a passenger object is a composition of some number of reservation objects.

How about in the other direction? A reservation object must have one connection back to a passenger. Such a "one and only one" constraint is too limiting, too narrow, to be considered composition; composition strategies won't help us there.

And so please note: composition is at work whenever you see an object connection constraint that is anything other than "1." For example:

0-1

2

n

1-n

all indicate composition is at work.

Footnote: A special kind of composition is called containment. Containment describes a composition in which the objects inside the composition are hidden from all outsiders; access to what is inside is strictly limited to access via the container object. Most composition is not containment. A passenger object, with regard to its reservation objects, is composition, yet not containment.. A passenger object, with regard to its internal low-level objects such as strings, is often a container, a special kind of composition.

Inheritance

Inheritance: what

Inheritance is a mechanism for extending the responsibilities defined in a class-meaning, to take the defined attributes, object connections, and methods of a class and add to them in some way.

We can define basic common attributes, object connections, and methods in a superclass (generalization class). Then we can add to them in one or more subclasses (specialization classes).

A subclass inherits everything that is defined in its superclass, accepting the superclass' definitions as its own.

Inheritance vs. interfaces

Inheritance extends the implementation of a method, not just its interface.

An interface establishes useful sets of method signatures, without implying an implementation of a method.

In Java, inheritance and polymorphism are expressed distinctly, with different syntax.

In C++, both concepts are expressed with a single syntax blurring the distinction between these very different mechanisms.

Inheritance: an example

Inheritance is great for showing a class that is always a special kind of some other class. Within a PD-component of an object model, inheritance most often occurs in three situations:

Figure 2-. The three most likely of inheritance within a PD-component of an object model

For example, consider transactions (notable moments or intervals of time). At Charlie's Charters, should our scope expand a bit, we might discover that we need something like this:

Figure 2-. Kinds of transactions

A reservation is a special kind of transaction. A purchase is a special kind of transaction. So far, so good.

Inheritance: benefits

Inheritance explicitly captures commonality, taking a class definition (what's the same: attributes, method signatures, and methods) and extending it with a new class definition (what's different: attributes, method signatures, and methods).

Inheritance is explicitly shown in an object model and in source code-something very nice indeed; so it's something good to use, when it's appropriate to do so.

So what's are the risks?

Inheritance: risks

Weak encapsulation within

Risk #1

Inheritance connotes strong encapsulation with other classes, yet weak encapsulation between a superclass and its subclasses.

The classes within a class hierarchy, with respect to each other, violate encapsulation, a fundamental tenet of object-oriented design.

Figure 2-. A change in a superclass ripples throughout its subclasses

If you change a superclass, we must check all of its subclasses, taking care of any rippling change effects. Here's why. If we change the implementation of a class that is a superclass, then we have effectively changed the implementation of all its subclasses (testers, please take note).

Obviating Risk #1

We can obviate this risk by designing a cohesive class hierarchy. We make sure the subclass is indeed a special kind of the superclass, not:

Also, we make sure that subclasses are indeed extensions. If we find the need to override or nullify inherited responsibilities, then we can:

Clumsy accommodation of objects that change subclasses

Risk #2

Inheritance connotes weak accommodation of objects that change subclasses over time.

For example, consider Person and its specializations:

Figure 2-. Inheritance clumsily accommodates objects that change subclasses.

What if we create an agent object, yet find out later that we need an agent-passenger object. Consider the transition from an agent object to an agent-passenger object. Here is what happens:

Figure 2-. Create, copy, delete-"transmute"

Every time an object in one subclass needs to change into an object in another class, we run into the "transmute" problem: create an object in another class, copy values to the new object, then delete the old object.

When we transmute, er uh, when an object in our design transmutes, we might lose information; if the values are not needed in the new object, the old values go away.

And when an object transmutes, it loses all sense of history (even the question, "so how long have you been an agent?" is not easily answered-and requires additional classes to track such change over time).

That makes change far more complex than it needs to be.

Obviating Risk #2

Use composition of roles. Composition is far better suited to continual change.

When an object needs additional role-specific responsibilities, add another role object (composition).

Adding a new role is easy. In fact, with kinds of roles, we could apply composition (a person and its roles) and inheritance (person roles, specializing into special kinds of roles). Check it out:

Figure 2-. With composition, adding a new role is a breeze;

if you have multiple kinds of roles, then inheritance can play along, too

Inheritance: when to use it

In bibliographic classification, developers of classification methods strive to find a way to classify objects (publications) so that:

(1) a subclass expresses "is a special kind of," not "is a role played by a"

(2) an object, once classified, will forever remain an object in that class.

In software classification, ones strives to find a way to classify objects (publications) so that:

(1) subclass expresses "is a special kind of," not "is a role played by a"

(2) an object, once classified, will forever remain an object in that class (it does not ever feel the need to transmute, to become an object in some other class).

In software classification, we add:

(3) a subclass extends, rather than overrides or nullifies, the responsibilities of its superclass

(4) a subclass does not extend the capabilities of what is merely a utility class (useful functionality you'd like to reuse)

(5) for PD classes, a subclass is a kind of role played, transaction, or device.

Here's how (5) fits in. We use inheritance in the PD component of an object model in three major ways:

- Role, specializing into special kinds of "participant" or "mission" roles

a role that a person plays:

person role (passenger, clerk, head clerk, manager, owner)

a role that a facility or piece of equipment plays:

aircraft mission (civilian mission, military mission)

- Transaction, specializing into special kinds of transactions (moments or intervals of time)

customer transaction (membership, reservation, payment, refund)

- Device, specializing into special kinds of devices

radar sensor (passive radar sensor, active radar sensor).

Inheritance: checkpoints

So here are the five checkpoints for effective use of inheritance:

"When to Inherit" Strategy: Inheritance to extend attributes and methods (yet encapsulation is weak within a class hierarchy, so use this mechanism in limited ways). Use it when you can satisfy these criteria:

(1) "Is a special kind of," not "is a role played by a"

(2) Never needs to transmute to be an object in some other class

(3) Extends, rather than overrides or nullifies

(4) Does not subclass what is merely a utility class (useful functionality

you'd like to reuse)

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Figure 2-. Inheritance criteria

Example: Composition (the norm)

Consider a person and the roles he plays, the hats he wears: passenger and agent. Composition is the norm, rather than the exception.

Yet could we apply inheritance here-with person as a superclass, plus passenger, agent, and agent-passenger as subclasses?

Figure 2-. Inheritance? Not a good idea, here.

Well, in Java we can't do that. It's a single-inheritance language. And the agent-passenger class inherits from more than one class-multiple inheritance.

Yet even if multiple inheritance were available, as it is in C++, is this an occasion to use inheritance? Or would composition be far better?

Check it out:

(1) "Is a special kind of," not "is a role played by a"

Fail. A passenger is not a kind of person; it's a role a person plays. An agent is not a kind of person; it's a role that a person plays.

(2) Never needs to transmute to be an object in some other class

Fail. It could change from passenger to agent to agent passenger, over time.

(3) Extends, rather than overrides or nullifies

Pass. Okay here.

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Fail. This expresses a kind of person.

Inheritance? No way. Composition applies here, as in most cases when building better object models. It's the norm.

Take a look at Charlie's Charters. We could pick any association in the object model-and find composition hard at work. Here is an example (with a couple of methods, for good measure):

Figure 2-. Person and two roles: passenger, agent

In Java, it looks like this:

public class Person extends Object {

#

// attributes / private / object connections

private Passenger passenger;

private Agent agent;

// methods / public / accessors for object connection values

public void addPassenger(Passenger aPassenger) {

this.passenger = aPassenger; }

public void removePassenger() { this.passenger = null; }

public Passenger getPassenger() { return this.passenger; }

public void addAgent(Agent anAgent) {

this.agent = anAgent; }

public void removeAgent() { this.agent = null; }

public Agent getAgent() { return this.agent; }

#

}

public class Passenger extends Object {

#

// attributes / private / object connections

private Person person;

// methods / public / accessors for object connection values

public Person getPerson() { return this.person; }

// constructors

// notice that there is no *default* constructor; a passenger must have

// a corresponding person object.

public Passenger(Person aPerson) {

// implicit call to superclass constructor super();

this.person = aPerson; }

#

}

public class Agent extends Object {

#

// attributes / private / object connections

private Person person;

// methods / public / accessors for object connection values

public Person getPerson() { return this.person; }

// constructors

// note that there is no *default* constructor; a passenger must have

// a corresponding person object.

public Agent(Person aPerson) {

// implicit call to superclass constructor super();

this.person = aPerson; }

#

}

Code notes: Each passenger object and agent object require a corresponding person object. Therefore, for Passenger and Agent, we create a constructor that requires a person as the parameter; and we do not provide a default constructor that takes no parameters.

Example: Both Composition and Inheritance

Yes, passenger and agent are special kinds of person roles.

So here, we can apply composition (person and its roles) in tandem with inheritance (person roles and special kinds of person roles). Like this:

Figure 2-. A person and its roles (composition); roles and special kinds of roles (inheritance)

Checking the inheritance usage criteria this time, we find:

(1) "Is a special kind of," not "is a role played by a"

Pass. Passenger and agent are special kinds of person roles (not roles that a "person role" plays).

(2) Never needs to transmute to be an object in some other class

Pass. A passenger object forever stays a passenger object; there is no need to transmute it to an object in some other class; the same is true for agent.

(3) Extends, rather than overrides or nullifies

Pass. Both subclasses extend the responsibilities defined in the superclass

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Pass. Now we have special kinds of roles. All is well.

A nice combination! Cool.

In Java, it looks like this:

public class Person extends Object {

#

// attributes / private / object connections

private Vector roles = new Vector();

// methods / public / accessors for object connection values

public void addRole(PersonRole aRole) {

this.roles.addElement( aRole); }

public void removeRole(PersonRole aRole) {

this.roles.removeElement(aRole); }

public Enumeration getRoles() {

return this.roles.elements(); }

#

}

Code notes: The above code does not check for an existing role when a role is added. That is, a person can add an agent role to its list of roles, a list that may already contain an agent role.

public abstract class PersonRole extends Object {

#

// attributes / private / object connections

private Person person;

// methods / public / accessors for object connection values

public Person getPerson() { return this.person; }

// constructors

// note that there is no *default* constructor; a person role must have

// a corresponding person object.

public PersonRole(Person aPerson) {

// implicit call to superclass constructor super();

this.person = aPerson; }

#

}

public class Passenger extends PersonRole {

#

// constructors

// notice that there is no *default* superclass constructor;

// an explicit call to the superclass constructor is required.

public Passenger(Person aPerson) { super( aPerson); }

#

}

public class Agent extends PersonRole {

#

// constructors

// notice that there is no *default* superclass constructor;

// an explicit call to the superclass constructor is required.

public Agent(Person aPerson) { super( aPerson); }

#

}

Code notes: Since Java implicitly creates a default constructor for a class without a constructor, and since the default constructor includes an implicit call to the superclass' default constructor, both Passenger and Agent require a constructor that calls PersonRole's non-default constructor.

Example: Inheritance (the exception)

Consider an example for Charlie's Charters. Suppose that we expand the context for the moment, including both reservations and purchases

Extend the responsibilities expressed by the model, this time to include purchases. We have two special kinds of transactions to deal with: reservation and purchase. Add a generalization class, called "transaction." And then extend it with two specialization classes, reservation and purchase classes.

Now we have two special kinds of transactions (moments or intervals of time): reservation and purchase. And some reservation objects will have a corresponding purchase object.

In object-model notation, it looks something like this:

Figure 2-. Special kinds of transactions

Yet is this a good use of inheritance? After all, we could use composition, like this:

Figure 2-. Use composition instead? Not here.

Yet we'd like to get the benefit of inheritance, in those special cases where it applies. After all explicitly capturing commonality in an object model and in source code is a very attractive thing.

So check out this use of inheritance, using the four-part checklist:

(1) "Is a special kind of," not "is a role played by a"

Pass. Reservation is a special kind of Transaction (not a role that a transaction plays); Purchase is a special kind of Transaction (not a role that a transaction plays).

(2) Never needs to transmute to be an object in some other class

Pass. A reservation object stays a reservation object, even if we create a corresponding purchase object at some point along the way.

In fact, a reservation object might need to know its corresponding purchase object. (Egad! That means composition between objects in the subclasses. Composition is indeed the norm; it is nearly everywhere.)

(3) Extends, rather than overrides or nullifies

Pass. Reservation extends the definition of Transaction (adding the dateTimeExpires attribute and the notifyPendingExpiration method); Purchase extends the definition of Transaction (adding the amount attribute and the calcDiscount method).

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Pass. Here, it's special kinds of transactions.

Actually, we could use both inheritance and composition here:

Then the result looks like this:

Figure 2- Inheritance-with a little composition, too

Here's it looks like-in Java:

public class Transaction extends Object {

#

// attributes / private

private int number;

private Date dateTime;

// methods / public / accessors for attribute values

public int getNumber() { return this.number; }

public void setNumber(int aNumber) { this.number = aNumber; }

public Date getDate() { return this.dateTime; }

public void setDate(Date aDateTime) { this.dateTime = aDateTime; }

#

}

public class Reservation extends Transaction {

#

// attributes / private

private Date dateTimeExpires;

// attributes / private / object connections

private Purchase purchase;

// methods / public / accessors for attribute values

public Date getDateTimeExpires() { return this.dateTimeExpires; }

public void setDateTimeExpires(Date aDateTime) { this.dateTimeExpires = aDateTime; }

// methods / public / accessors for object connection values

public void addPurchase(Purchase aPurchase) {

this.purchase = aPurchase; }

public void removePurchase() { this.purchase = null; }

public Passenger getPurchase() { return this.purchase; }

#

}

public class Purchase extends Transaction {

#

// attributes / private

private float amount;

// attributes / private / object connections

private Reservation reservation;

// methods / public / accessors for attribute values

public float getAmount() { return this.amount; }

public void setAmount(float anAmount) { this.amount = anAmount; }

// methods / public / accessors for object connection values

public Reservation getReservation() { return this.reservation; }

// constructors

// note that there is no *default* constructor; a purchase must have

// a corresponding reservation.

public Purchase(Reservation aReservation) {

// implicit call to superclass constructor super();

this.reservation = aReservation; }

#

}

Example: Inheritance, in Need of Adjustment

Consider another possibility. Switch over to Zoe's Zones for this one.

Remember the sensor class? It looks like this (including the object connection to some number of problem intervals, to round out this example):

Figure 2-. A sensor and its problem intervals

Now we find out that we will be working with remote sensors, too. Instead of activating and monitoring a remote sensor, all we can do is request a reading from it (getting back a value and its operational state).

Extend the responsibilities expressed by the model, this time to include a new kind of sensor. Extend the sensor class with one specialization class, remote sensor.

We could add a subclass like this:

Figure 2-. An example of inheritance gone awry

Yet for a remote sensor object, we don't need an object connection to a problem interval. Nor do we need activate, monitor, or assess, for interacting with a remote sensor. The little X's in the figure mark the things a remote sensor nullifies-not needed, never used.

Is this a good use of inheritance?

(1) "Is a special kind of," not "is a role played by a"

Pass. Remote Sensor is a special kind of Sensor (not a role that a sensor plays)

(2) Never needs to transmute to be an object in some other class

Pass. A remote sensor remains a remote sensor-not under our direct control.

(3) Extends, rather than overrides or nullifies

Fail. Ahhh, here's the catch. The remote sensor class nullifies, has no use for a connection to problem interval or the method trio (activate, monitor, assess).

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Pass. Here, it's special kinds of devices.

We're close. Inheritance does apply, we just need to rearrange the hierarchy a bit.

Try this: extend the responsibilities expressed by the model, this time to include activatable sensors and remote sensors. We have two special kinds of sensors to deal with: activatable sensors and remote sensors. We can keep a generalization class, called "sensor." And then we can extend it with two specialization classes, activatable sensor and remote sensor.

Figure 2-. Rearranging the class hierarchy a bit

Now criterion number 3 is satisfied (extends, rather than nullifies)-along with the others. We've made it-good inheritance!

Example: Thread

Suppose we want to add a thread (a copy of a program, running with the same data as other copies of that program) to an object model.

Is it time for composition? Or inheritance?

Figure 2-. A kind of thread?

Note that the superclass has an outer rounded rectangle around it. That outer rounded rectangle indicates that one might have objects of that class, too. (In other words, it's a concrete class, not an abstract class.)

In Java, it looks like this:

public class Sensor extends Thread {

#

// attributes / private

private int number;

private int range;

// methods / public / override

public void run () { // code goes here }

#

}

Again, consider the exception: inheritance. Is a sensor s kind of thread? Does specialization apply here?

(1) "Is a special kind of," not "is a role played by a"

Fail. A sensor is not a kind of thread.

(2) Never needs to transmute to be an object in some other class

Pass. Okay here.

(3) Extends, rather than overrides or nullifies

Pass. Okay here.

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Not applicable (thread is an infrastructure class, not a PD class)

Composition to the rescue. Here's how it works. The Java runtime system manages threads. We define a sensor class with a "run" method. We create a sensor object; we create a thread object. Finally, we send the sensor object to the thread object, asking it to run what we are passing to it.

Extend the responsibilities of a sensor object with a thread object.

Figure 2-. A sensor and its threads (much more, in the threads chapter)

The Thread class comes with Java. Here's what the Sensor class looks like-in Java:

public class Sensor extends Object implements Runnable {

#

// attributes / private

private int number;

private int range;

// attributes / private / object connections

private Thread monitorThread;

private Thread assessThread;

// methods / public / Runnable implementation

public void run() { // code goes here }

#

}

Example: Applet

Now take a look at the Applet class in Java.

Inheritance chain: Applet is a special kind of Panel is a special kind of Container is a special kind of Component is a special kind of Object.

Figure 2-. Applet and its superclasses

Is this a good use of inheritance? Check it out:

(1) "Is a special kind of," not "is a role played by a"

Pass. Applet is a special kind of Panel is a special kind of Container is a special kind of Component is a special kind of Object-not a role played.

(2) Never needs to transmute to be an object in some other class

Pass. An applet remains an applet.

(3) Extends, rather than overrides or nullifies

Pass. An applet extends what a panel is all about.

(4) Does not subclass what is merely a utility class

Pass. Okay here.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Not applicable (applet is an infrastructure class, not a PD class)

So yes, this is a good use of inheritance.

When we extend (inherit from) Applet, we get all the goodies for gluing together what we need in an applet. Typically:

What happens when we want to add our own special kind of applet? Whoops! By now, the wording of that question should tip us off-"a special kind of."

Add specializations that are indeed special kinds of applets, for example, a "ReservationUI" applet-and in doing so, extend the responsibilities of the applet class, with inheritance.

Figure 2-. A special kind of applet

Here's what it looks like-in Java:

public class ReservationUI_Applet extends Applet {

#

// methods / public / Applet override

public void init() {

// initialization code goes here }

#

}

Example: Observable

Now consider the Observable class in Java.

Figure 2-. Observable and its superclass

Observable is a class that is used in notification. Or at least, it is supposed to be used that way.

Observable consists of a number of methods, making it easier for an observable object to notify other objects about a state change.

Figure 2-. A special kind of observable

Suppose that we make Reservation a subclass of Observable, so it acts as an observable. That way, a reservation object can notify other objects (notably UI objects) whenever it changes.

Figure 2-.

Is this a good use of inheritance? Check it out:

(1) "Is a special kind of," not "is a role played by a"

Fail. A reservation is not a special kind of observable. (Well, maybe, in a real abstract sense. Somehow, it doesn't feel right. Let's check the other criteria.)

(2) Never needs to transmute to be an object in some other class

Pass. A reservation object remains a reservation object.

(3) Extends, rather than overrides or nullifies

Pass. Reservation extends what Observable is all about.

(4) Does not subclass what is merely a utility class

No. Observable is a utility class, a collection of useful methods-nothing more.

(5) Within PD: Expresses special kinds of roles, transactions, or devices.

Not applicable (Observable is a utility class, not a PD class).

So inheritance does not apply here-even though it's set up that way in Java.

There is an excellent alternative. Yet that is the subject of subsequent chapters.

Summary

In this chapter, we've explored two mechanisms for extending a design: composition and inheritance.

Inheritance is useful in limited contexts. Composition is useful in nearly every context.

Figure 2-. Summary: inheritance vs. composition

Inheritance was all the rage in the early days of object-oriented development. Yet over time, designers have discovered that inheritance is effective only within certain contexts.

Indeed, composition, in tandem with interfaces (Chapter 3), is far more common, for more generally useful, and much closer to the heart of good object-oriented design.

Here are the strategies that you've learned and applied in this chapter:

"Composition" Strategy: Use composition to extend responsibilities by delegating work to other objects.

"When to Inherit" Strategy: Inheritance to extend attributes and methods (yet encapsulation is weak within a class hierarchy, so use this mechanism in limited ways). Use it when you can satisfy these criteria:

(1) "Is a special kind of," not "is a role played by a"

(2) Never needs to transmute to be an object in some other class

(3) Extends, rather than overrides or nullifies

(4) Does not subclass what is merely a utility class (useful functionality you'd like to reuse)

cover Back to internet programming Links (ip-Links) page
Back to Java OO Design & Coding Standards (joodcs) page
Back to "Java Design" book page
Back to Table of Contents
Back to Chapter 1
Source http://www.oi.com/2.htm
On to Chapter 3

Home | Search | What's New | Workshops | Books
Software | Support | Free Goodies | Newsletters | Feedback