Java Design:
Building Better Apps and Applets

Chapter 3: Design with Interfaces

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 4

Design with Interfaces

In this chapter you'll explore Java-style interfaces: what they are, why they are important, and four major contexts where you'll find them very helpful.

What

Interfaces are the key to pluggability, the ability to remove one component and replace it with another. Consider the electrical outlets in your home: the interface is well-defined (plug shape, receptacle shape, voltage level for each prong); you can readily unplug a toaster and plug in a java-maker-and continue on your merry way.

Design with interfaces? Yes!

An interface is a common set of method signatures that you define for use again and again in your application. It's a listing of method signatures alone; there is no common description, no source code behind any of those method signatures.

Footnote: Java expresses inheritance and polymorphism distinctly, with different syntax. C++ expresses both concepts with a single syntax; it blurs the distinction between these very different mechanisms, resulting in overly complex, overly deep class hierarchies. (We design with interfaces either way; Java makes it easier for us to express that design in source code.)

Footnote: In Smalltalk, interfaces (called protocols) are agreed upon by convention and learned by reading source code. In C++, interfaces are implemented as classes with no implementation (everything inside is declared as being "pure virtual").

Footnote: Java interfaces can include constants, too. That provides a convenient way to package useful constants when programming, yet has no impact on effective design.

An interface describes a standard protocol, a standard way of interacting with objects in classes which implement that interface.

Working with interfaces requires that we (1) specify the interface and (2) specify which classes implement that interface.

Let's begin with a simple interface, called IName. IName consists of two method signatures, the accessors getName and setName.

Figure 3-. An interface

By convention, interface names are capitalized: the IName interface. When referring to an object of a class that implements an interface, we don't capitalize it: a name object, meaning, an object in a class that implements IName.

By convention, Java interface names end with the suffix "-able" or "-ible"-or (occasionally) "-er."

Footnote: Requiring interface names to end in -able or -ible is a bit too complicated a convention. If you'd like to adopt that convention, take note of English-language spelling rules:

(1) Drop a silent "e" before adding "-able."

(2) -able and -ible: Check a dictionary. If it's not listed, look at other forms of the word, to see which letter might make sense. (Again, a bit too complicated for day-to-day use.)

By convention in SOM and in ActiveX, interface names begin with the prefix "I."

By convention in this book, interface names begin with the prefix "I" and are followed by:

Here, the interface name is "I"+ a noun, like so:

In Java, an IName interface might look something like this:

public interface IName {

String getName();

setName(String aName); }

A class which implements the IName interface promises to implement the "get name" and "set name" methods-in a way which is appropriate for that class. The "get name" method returns the name of an object. The "set name" method establishes a new name for an object.

Figure 3-. A class which promises to implement an interface

The IName interface describes a standard way to interact with an object in any class that implements that interface.

This means that as an object in any class, you could hold an IName object (meaning, objects within any number of classes which implement the IName interface). And you could ask an IName object for its name, without knowing or caring about what class that object happens to be in.

Why

Why interfaces?

The problem

Over the years, we've encountered a classic barrier to:

Yes, a barrier within object-oriented design itself.

Some background is in order. All objects interact with other objects to get something done. Yes, an object can answer a question or calculate a result all by itself; yet even then, some other object does the asking. In short: objects interact with other objects. That's why scenario views are so significant, modeling time-ordered sequences of object interactions.

In object models and scenario views, an object must be within a specified class. And that is the big problem.

What is the element of reuse? It's not just a class; objects in that class are interconnected with objects in other classes. It's some number of classes, the number of classes in a scenario (or even worse, the total number of classes contained in overlapping scenarios).

What's the impact, in term of pluggability? If you want to add another class of objects, one that can be plugged-in as a substitute for an object in another class already in a scenario view, you are in trouble. There is no pluggability there. Instead, you must add object connections, build another scenario view, and implement source code behind it all.

The problem: each object connection and each message-send is hardwired to objects in a specific class, impeding pluggability (and extensibility and flexibility, too).

Traditionally, objects in a scenario view are hardwired to each other. Yet if the "what I know" (object connections) and "who I interact with" (object interactions) are hardwired to just one class of objects, then pluggability is non-existent; adding a new class means adding: the class itself, object connections, scenario views, plus changes to other classes-in the design and in source code.

A partial solution

We'd like a more flexible, extensible, and pluggable approach, one that would let us add in new classes of objects with no change in object connections or message-sends.

There is a partial solution.

If you want to add a new class which is a subclass of one of the classes of objects participating in a scenario, then you can fit that in with no problems. You can add a specialization class to your object model, add a comment to your scenario view that objects from the specialization class are applicable too, and you are ready to go.

However, if inheritance does not apply, or if you have already used inheritance in some other way (keeping in mind that Java is a single inheritance language), then this partial solution is no solution at all.

Flexibility, extensibility, and pluggability- that's why.

Interfaces enhance, facilitate, and even make possible the flexibility, extensibility, and pluggability that we so desire.

Interfaces shift one's thinking about an object and its connections with other objects.

"Challenge Each Object Connection" Strategy: Is this connection hard-wired to objects in that class (simpler)? Or is this a connection to any object which implements a certain interface (more flexible, extensible, pluggable)?

For an object and its connections to other objects, you ask, "Is this connection hard-wired to objects in that class? Or is this a connection to any object which implements a certain interface?" If it's the latter, you are in effect saying, "I don't care what kind of object I am connected to, just as long as that object implements the interface that I need."

Interfaces also shift one's thinking about an object and the kinds of objects that it interacts with during a scenario.

"Challenge Each Message-Send" Strategy: Is this message-send hard-wired to object in that class (simpler)? Or is this a message-send to any object that implements a certain interface (more flexible, extensible, pluggable)?

For each message-send to another object, you ask, "Is this message-send hard-wired to object in that class? Or is this a message-send to any object that implements a certain interface? If it's the latter, you are in effect saying, "I don't care what kind of object I am sending messages to, just as long as that object implements the interface that I need."

So, when you need the flexibility, you specify object connections (in object models) and message-sends (in scenario views) to objects in any class that implements the interface that is needed, rather than to objects in a single class (or its subclasses).

Interfaces loosen up coupling, make parts of a design more interchangeable, and increase the likelihood of reuse-all for a (very) modest increase in design complexity.

Interfaces express "is a kind of" in a very limited way, "is a kind that supports this interface." This gives us the categorization benefits of inheritance; at the same time, it obviates the major weakness of inheritance: weak encapsulation within a class hierarchy.

Interfaces give composition a much broader sphere of influence. With interfaces, composition is flexible, extensible, and pluggable (composed of objects that implement an interface), rather than hard-wired to just one kind of object (composed of objects in just one class).

Interfaces reduce the otherwise compelling need to jam many, many classes into a class hierarchy with lots of multiple inheritance. In effect, using interfaces streamlines how one uses inheritance: use interfaces to express generalization-specialization of method signatures; use inheritance to express generalization-specialization of interfaces implemented-along with additional attributes and methods.

Interfaces give you a way to separate method signatures from method implementations. So you can use them to separate UI method signatures from operating-system dependent method implementations; that's exactly what Java's Abstract Windowing Toolkit (AWT) does. You could do the same for data management, separating method signatures from vendor-dependent method implementations. You can do the same for problem-domain objects, too-as you'll see in this chapter.

Sound-bite summary: Why interfaces? Interfaces give us a way to establish object connections and message-sends to objects in any class that implements a needed interface-without hardwiring to object connections or message-sends to a specific class of objects.

The larger the system, and the longer the potential life span of a system, the more significant interfaces become.

Four Major Contexts

Factoring out every method signature into a separate interface would be overkill-you'd make your object models more complex and your scenario views rather abstract.

In what contexts should you apply interfaces?

You can factor-out method signatures into interfaces in a variety of contexts. In this chapter, you'll discover four major contexts in which interfaces really help:

Factor-out repeaters

Factor-out to a proxy

Factor-out for analogous apps

Factor-out for future expansion.

Factor-Out Repeaters

We begin with the simplest use of interfaces. Let's use them to factor-out common method signatures, bring a higher level of abstraction (and an overall visual simplification) to an object model.

This is a modest yet important use of interfaces.

"Factor-Out Repeaters" Strategy: Factor-out method signatures that repeat within your object model. Resolve synonyms into a single signature. Generalize overly-specific names into a single signature. Reasons: explicitly capture that commonality; bring a higher level of abstraction into the model.

Look for repeating method signatures and factor them out.

Example: calcTotal in one class, calcTotal in another class

Factor out that method signature, into an ITotal interface.

Label each class as one that implements the ITotal interface.

Now, look for method signatures that are synonyms. Pick a common method signature and factor it out.

Example: calcTotal in one class, determineTotalAmount in another class

Pick a synonym: calcTotal.

Factor out that method signature, into an ITotal interface.

Label each class as one that implements the ITotal interface.

Next, take each method signature and generalize it. (Well, not to the point of obscurity; a method name like "process it" or "calculate it" would not be very helpful, would it?) Then look for method signatures that are synonyms; finally pick a common method signature and factor it out.

Example: calcSubtotal in one class, calcTotal in another class, calcGrandTotal in another class

Pick a synonym: calcTotal.

Factor out that method signature, into an ITotal interface.

Label each class as one that implements the ITotal interface.

When factoring-out interfaces, you also need to consider the return types and parameter types; they must match up, too. In fact, in an object model, you could include a complete method signature:

return type + method name + parameter types + exceptions.

Yet, no matter how well-intentioned, that is usually a mistake. It takes up far too much screen real estate. Far better: an effective object model of the design, plus source code with details, side-by-side.

Example: Charlie's Charters' lunch counter

Okay then. One way to discover interfaces is to factor-out repeaters. Consider a point-of-sale application, for the lunch counter at Charlie's Charters:

Figure 3-. Repeating method signatures

In Java, it looks like this:

public Customer extends Object {

#

// methods / public / conducting business

public float howMuch() { // code goes here }

#

}

public Sale extends Object {

#

// methods / public / conducting business

public float calcTotal() { // code goes here }

public float calcTax() { // code goes here }

#

}

public SaleLineItem extends Object {

#

// methods / public / conducting business

public float calcTotal() { // code goes here }

public float calcTax() { // code goes here }

#

}

public Store extends Object {

#

// methods / public / conducting business

public float grandTotal() { // code goes here }

public int howMany() { // code goes here }

#

}

public Item extends Object {

#

// methods / public / conducting business

public int howMany() { // code goes here }

#

}

Apply the "factor out repeaters" strategy.

You can factor-out calcTotal; no problem there.

Now look for synonyms.

calcTotal and howMany could be synonyms-yet the return types don't match. That's a problem. We could check the return types to see if they are synonyms for each other; moreover, we could try generalizing each return type and see if that might help us. Here, though, calcTotal returns a floating-point number; "how many" returns an integer. You cannot combine different method signatures into one interface.

Let's keep looking. calcTotal and howMuch are synonyms-and the return types match (both return a floating point value). One or the other will do just fine; choose calcTotal and factor it out.

grandTotal is a specialized name for calcTotal. Use calcTotal for both.

Here is the result:

Figure 3-. Factor-out repeating method signatures

In Java, it looks like this:

public interface ICount {

int howMany(); }

public interface ITotal {

float calcTotal(); }

public interface ITax {

float calcTax(); }

public interface ISell extends ITotal, ITax {}

public Customer extends Object implements ITotal {

#

// methods / public / ITotal implementation

public float calcTotal() { // code goes here }

#

}

public Sale extends Object implements ISell {

#

// methods / public / ISell implementation

public float calcTotal() { // code goes here }

public float calcTax() { // code goes here }

#

}

public SaleLineItem extends Object implements ISell {

#

// methods / public / ISell implementation

public float calcTotal() { // code goes here }

public float calcTax() { // code goes here }

#

}

public Store extends Object implements ITotal, ICount {

#

// methods / public / ITotal implementation

public float calcTotal() { // code goes here }

// methods / public / ICount implementation

public int howMany() { // code goes here }

#

}

public Item extends Object implements ICount {

#

// methods / public / ICount implementation

public int howMany() { // code goes here }

#

}

Especially note this:

public interface ISell extends ITotal, ITax {}

Here, an interface extends two other interfaces. Multiple inheritance?

Well, yes and no.

Yes, the new interface is a combination of the other two interfaces. Yes, ISell is a special kind of ITotal and a special kind of ITax.

No, it's not inheritance; only method signatures are involved; there is absolutely no implementation behind those method signature.

We don't really think of it as inheritance, either.

We think of interfaces as useful method-signature descriptions, ones that we can conveniently mix-and-match with the "extends" keyword.

One way to visualize it: picture a stack of index cards; each card has an interface name and its method signatures on it; grab whatever combination is useful to you (ITotal, ITax); name that useful combination (ISell).

Example: Simplify and identify object-model patterns.

Together with David North, we have cataloged 31 object-model patterns: templates of objects with stereotypical responsibilities and interactions. Those patterns are documented at www.oi.com/handbook and (more thoroughly) in the book, Object Models: Strategies, Patterns, and Applications.

One of the more puzzling matters has been how to show those patterns within source code. Some have proposed adding extra classes of objects, managing each pattern; yet that seemed like overkill to us.

Interfaces offer an interesting twist. And the simplest use of interfaces, factoring out common method signatures, takes on some added significance.

Consider this transaction pattern, called "transaction-transaction line item."

Figure 3-. An object-model pattern

Other patterns use attributes and services with exactly the same names. So everything can be factored out into interfaces.

First, for full impact, first add-in attribute accessors:

Figure 3-. An object-model pattern, with attribute accessors

In Java, it looks like this:

public class Transaction extends Object {

#

// attributes / private

private int number;

private Date dateTime;

private String status;

// attributes / private / object connections

private Vector transactionLineItems = new Vector();

// methods / public / conducting business

public float calcTotal() { // code goes here }

public Enumeration rank() {

// return an enumeration with ranked transaction line items

// code goes here }

// methods / public / accessors for attribute values

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

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

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

public void setDateTime(Date aDateTime)

{ this.dateTime = aDateTime; }

public String getStatus() { return this.status; }

public void setStatus(String aStatus) { this.status = aStatus; }

#

}

public class TransactionLineItem extends Object {

#

// attributes / private

private int quantity;

private String status;

// attributes / private / object connections

private Transaction transaction;

// methods / public / conducting business

public float calcTotal() { // code goes here }

public int rate() { // code goes here }

// methods / public / accessors for attribute values

public int getQuantity() { return this.quantity; }

public void setQuantity(int aQuantity) { this.quantity = aQuantity; }

public String getStatus() { return this.status; }

public void setStatus(String aStatus) { this.status = aStatus; }

#

}

Second, apply the "factor out repeaters" strategy.

Figure 3-. Factor-out repeaters

In Java, it looks like this:

public interface IRank {

Enumeration rank(); }

public interface IRate {

int rate(); }

public interface ITotal {

float calcTotal() ; }

public interface INumber {

int getNumber();

void setNumber(int aNumber); }

public interface IDateTime {

Date getDateTime();

void setDateTime(Date aDate); }

public interface IQuantity {

int getQuantity();

void setQuantity(int aQuantity); }

public interface IStatus {

String getStatus();

void setStatus(String aStatus); }

public class Transaction extends Object

implements IRank, ITotal, INumber, IDateTime, IStatus {

#

// class definition here

#

}

public class TransactionLineItem extends Object

implements IRate, ITotal, IQuantity, IStatus {

#

// class definition here

#

}

Now, go for the gold: factor out the interfaces within each "pattern player"-making pattern players explicit in the design (and ultimately, in source code).

Factor 3-. Factor all the way out, so you can mark out pattern players

In Java, it looks like this:

public interface ITransaction

extends ITotal, IRank, INumber, IDateTime, IStatus {}

public interface ILineItem

extends ITotal, IRate, IQuantity, IStatus {}

public class Transaction extends Object

implements ITransaction {

#

// class definition here

#

}

public class TransactionLineItem extends Object

implements ILineItem {

#

// class definition here

#

}

Factor-Out to a Proxy

"Factor-Out to a Proxy" Strategy: Factor-out method signatures into a proxy, an object with a solo connection to some other object. Reasons: simplifies the proxy within an object model and its scenario views.

Recognizing a proxy

Another way to bring interfaces into your design is to factor-out method signatures into a proxy. Consider person and passenger in Charlie's Charters' reservation system, this time with get and set accessors included:

Figure 3-. Person, with accessors

In Java, it looks like this:

public class Person extends Object {

#

// attributes / private

private String name;

private String address;

// attributes / private / object connections

private Passenger passenger;

// methods / public / accessors for attribute values

public String getName() { return this.name; }

public void setName(String aName) { this.name = aName; }

public String getAddress() { return this.address; }

public void setAddress(String anAddress)

{ this.address = anAddress; }

// 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 class Passenger extends Object {

#

// attributes / private

private int number;

private String type;

// attributes / private / object connections

private Person person;

// methods / public / accessors for attribute values

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

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

public String getType() { return this.type; }

public void setType(String aType)

{ this.type = aType; }

// 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; }

#

}

Passenger has a "one and only one" connection with a person object. Whenever an object has a "one and only one" connection with another object, then that object can act as a proxy for the other object.

Life without a proxy

Proxy? Why bother? Well, consider this "before" picture, before we let one object act as a proxy for another. Suppose that you've identified a passenger object, and would like to know its name and address. What does the scenario view look like?

Figure 3-. Asking a passenger for its person object, then asking a person object for its name and address

Life with a proxy

A proxy answers questions on behalf of another. A proxy provides a convenient interface. Check it out:

Figure 3-. Person and Passenger, both with accessors

And the scenario view, from the perspective of a reservation object, looks like this:

Figure 3-. Asking a proxy for what you need

In Java, it looks like this:

public class Passenger extends Object {

#

// methods / public / accessors for Person's attribute values

public String getName() { return this.person.getName(); }

public void setName(String aName) { this.person.setName(aName); }

public String getAddress() { return this.person.getAddress(); }

public void setAddress(String anAddress)

{ this.person.setAddress(anAddress); }

#

}

Now you can ask a passenger for its name and address, rather than asking a passenger for its person object, and then interacting with that person object.

Yes, a passenger object will still privately interact with its person object. We could show that interaction one time, in a separate scenario view, like this:

Figure 3-. Behind the scene: a proxy interacting with the one it represents (boring)

Yet that really is rather boring-not something we would not normally sketch out.

Hence, with a proxy, scenario views become simpler; the details about whomever is being represented by the proxy is shielded from view.

Introducing a helpful interface for a proxy and the one it represents

Okay, so now let's bring interfaces into the picture. Factoring-out commonality, you get this:

Figure 3-. Person and Passenger, with common interfaces

In Java, it looks like this:

public interface IName {

String getName();

void setName(String aName);

}

public interface IAddress {

String getAddress();

void setAddress(String anAddress);

}

public class Person extends Object implements IName, IAddress {

#

// class definition here

#

}

public class Passenger extends Object implements IName, IAddress {

#

// class definition here

#

}

We can combine these two interfaces, like so:

Figure 3-.Person and Passenger, with a single, combined interface

In Java, it looks like this:

public interface INameAddress extends IName, IAddress {}

public class Person extends Object implements INameAddress {

#

// class definition here

#

}

public class Passenger extends Object implements INameAddress {

#

// class definition here

#

}

Now bring agent into the picture:

Figure 3-. A person is composed of one or more person roles; a person role specializes into different kinds of person roles.

In Java, it looks like this:

public class Person extends Object

implements INameAddress {

#

// class definition here

#

}

public abstract class PersonRole extends Object implements INameAddress {

#

// class definition here

#

}

public class Passenger extends PersonRole {

#

// class definition here

#

}

public class Agent extends PersonRole {

#

// class definition here

#

}

Now consider a NameAddressUI object.

It's a user interface (UI) object, one that contains a number of smaller, hand-crafted or GUI-builder-generated UI objects: text fields, buttons, scrollable lists, and the like.

In addition, and more importantly (from an object-modeling perspective), a NameAddressUI object knows some number of objects in classes which implement the INameAddress interface.

NameAddressUI is not hard-wired to objects in just one class. Instead, it works with objects from any class that implements the INameAddress interface.

Check it out:

Figure 3-. Each name-address UI object is composed of a collection of INameAddress objects.

In Java, it looks like this:

public class NameAddressUI {

#

// attribute / private / object connection

private Vector nameAddresses = new Vector();

// method / public / accessor for object connection values

public void addNameAddress(INameAddress aNameAddress) {

// only add objects of the type INameAddress to the vector

this.nameAddress.addElement(aNameAddress) ; }

#

}

Interfaces change the very nature of object connections-and get all of us to rethink what object modeling is all about.

Here, a UI object holds a collection of objects from any class that implements a specific interface. This shifts an object-model builder's attention to "what behavior does that object need to provide" rather than "what class(es) of objects should I limit myself to."

With interfaces, an object model gains better abstraction and simpler results.

Now, take a look at a corresponding scenario view:

Figure 3-.A name-address UI object, interacting with an INameAddress object

put "Implementer" on the next line.

Interfaces change the very nature of a scenario, a time-ordered sequence of object interactions-and get all of us to rethink what scenarios are all about.

In this scenario, a UI object sends a message to any object in a class which implements the needed interface. For the receiving object: it no longer matters where its class is in the class hierarchy; it no longer matters if its class spells out a different implementation (time vs. size tradeoffs will always be with us).

With interfaces, your attention shifts from "what class of objects am I working with now" to "what's the interface, what's the behavior which I need from whatever kind of object I might work with, now or in the future."

With interfaces, you spend more time thinking about the behavior that you need, rather than who might implement that behavior.

With interfaces, each scenario view delivers more impact within each scenario, and reduces redundancy across related scenarios.

The impact? Reuse within the current app. And greater likelihood of reuse in future apps.

Factor-Out for Analogous Apps

"Factor-Out for Analogous Apps" Strategy: Factor-out method signatures that could be applicable in analogous apps. Reason: increase likelihood of using and reusing off-the-shelf classes.

You can use the "factor out repeaters" strategy to increase the level of abstraction within an object model and scenario views within the problem domain you are currently working in.

This "factor-out for analogous apps" strategy takes an even broader perspective. You can use this strategy to achieve use and reuse across a family of analogous applications.

Here's how.

Categorize to your heart's content.

You can categorize business apps in different ways. If inheritance were your only categorization mechanism, you could go absolutely crazy. How could you decide upon just one or just a few ways to categorize what you are working on?

Yet now you have interfaces. And you can use them to categorize classes of objects in multiple ways, across a variety of dimensions.

Consider business apps. Two key (yet certainly not all-inclusive) categories are sales and rentals. In a sales system, some good are sold for a price.. And that's that. So we could categorize certain classes of objects as being sellable, perhaps reservable, too.

In a rental system, talent, equipment, or space is rented for a date or for an interval of time; the good are still there; the goods are rented again and again and again. Here, we could classify certain classes of objects as being rentable, perhaps reservable, too.

Categorize Charlie's Charters' business.

What kind of business is Charlie's Charters in? Sandpiper is in the rental business: it rents space on a scheduled flight for a specific date.

For Sandpiper itself, for a flight description, we can reserve space on a scheduled flight. We can ask it if a seat is available; we can ask it to reserve a seat; we can ask it to cancel a seat.

Figure 3-. Methods for reserving space on a scheduled flight

Now consider a UI object who knows one or more flight description objects. Without interfaces, it looks like this:

Figure 3-. A UI class, custom-crafted for a flight description

The corresponding scenario view look like this:

Figure 3-. UI objects, interacting with objects in just one class (hardwired object interactions)

How can interfaces help in this context?

Sandpiper is a no-frills airline. It reserves space on a scheduled flight; it does not reserve specific seat numbers. (Not a big deal; adding SeatMap, Seat, and SeatAssignment classes would take care of that.)

For the Sandpiper app, we are interested in reserving space for a given date. We could use an interface called IDateReserve:

Figure 3-. The IDateReserve interface

We need to add the passenger as a parameter for reserve and cancel. However, since we want this interface to be general, the type of the parameter should be that of an Object. Let's give it the name "reserver"-and so we have:

reserve (date, reserver) and

cancel (date, reserver).

Here is what it looks like in Java:

public interface IDateReserve {

boolean available(Date aDate);

Object reserve(Date aDate, Object reserver);

boolean cancel(Date aDate, Object reserver); }

Code notes: available and cancel return boolean results. reserve returns an object, keeping the interface flexible (we aren't needlessly limiting the interface to objects in a specific class or its subclasses). The object that gets that returned object must cast the result into whatever kind of object it expects to get back.

Note that the method signatures are generalized a bit-so they can be applied in within any system that has IDateReserve elements within it.

Why bother extracting out this analogous interface? Simply put: we are looking for an interface that makes it easy for objects that know how to interact with that interface to "plug in" and make use of that interface. Having off-the-shelf UI components that sport commonly-used interfaces saves design, development, and testing time. Very nice indeed.

For example, if I have an object which knows how to interact with an object in any class that implements IDateReserve , then I can use and reuse that object in any app with IDateReserve objects in it. Note that all we care about is the interface; we are free from having to consider the specific class or classes of objects that we might want to interact with. This gives us new-found freedom within object-oriented design.

An aside: some related interfaces

A variation on this theme is IDateTimeReserve (not needed here, since a flight description specifies a time of departure). Yet if we needed it, it would look like this:

Figure 3-. The IDateTimeReserve interface

Let's consider analogous systems, other rental systems.

For video rentals, we'd reserve a title for a date (for example, this Saturday). This is another place where we could use that same IDateRerserve interface.

For hotel rooms, we'd be interested in reserving a certain kind of room (concierge level) for an interval (for example, from the 5th to the 9th). We could use an interface called IDateIntervalReserve.

Figure 3-. The IDateIntervalReserve interface

For car rentals, we'd reserve a certain kind of car (full-size four-door) for an interval (for example, from the 5th at 5pm until the 9th at 9pm). We could use an interface called IDateTimeIntervalReserve.

Figure 3-. The IDateTimeIntervalReserve interface

Using IDateReserve for Charlie's Charters

Okay, then. For Charlie's Charters, we need an IDateReserve interface, like this:

Figure 3-. The flight description class implements the IDateReserve interface.

We can use or reuse any object that knows how to interact with an object in a class that implements the IDateReserve interface.

For example, a "date reservation" user interface could interact with an object in any class that implements IDateReserve-a flight reservation object, a video title object, whatever.

With interfaces, we get newly-found flexibility. Now UI objects can connect with an object in any class that implements an interface.

Figure 3-. UI objects, connected to objects in classes that implement a given interface (flexible object connections)

Figure 3-. UI objects, interacting with objects in classes that implement a given interface (flexible object interactions)

With interfaces, our attention shifts from "what class of objects can I interact with?" to "what's the interface that I can interact with?"

Using IDateReserve in other apps, too

Let's consider another date reservation example. Suppose you are designing a temporary help system, in which each worker and each piece of equipment is reservable for a date. In this case, a "daily work order" object can interact with any objects in classes that implement the IDateReserve interface.

Figure 3-. Each daily work order object is composed of a collection of IDateReserve objects.

Figure 3-. Each daily work order object interacts with IDateReserve objects.

Today, a daily work order might be a collection of workers and pieces of equipment. Next year, it might be a collection of workers, pieces of equipment, and work space.

The change impact?

Add a new class to your object model: Workspace. Be sure it implements the IDateReserve interface. Connect it with whatever object connections it might need.

Figure 3-. Each daily work order object is still composed of a collection of IDateReserve objects.

No change to your scenario view is needed. The interaction between a daily work order its IDateReserve objects remain exactly the same.

A daily work order holds a collection of IDateReserve objects. What if it also holds other objects in that collection, objects from classes that don't implement IDateReserve? In this case, a daily work order object can ask an object if is an instance of IDateReserve, and then if it is, use the interface to interact with that object.

FOOTNOTE: In C++, information about what class an object is in is called run-time type information (RTTI). In Smalltalk, information about what class an object is a standard query that one can ask any object.

The point of all this is expandability. By using interfaces, your object model and scenario views are organized for change. Rather than being hard-wired to a limited number of classes of objects, your design can accommodate objects from present or future classes, just as long as its class implements the interface that you need.

Factor-Out for Future Expansion

"Factor-Out for Future Expansion" Strategy: Factor-out method signatures now, so objects from different classes can be graciously accommodated in the future. Reason: change flexibility.

You can use interfaces as a futurist, too. What if you are wildly successful on your current project? Simply put: the reward for work well done is more work.

So what is next? What other objects might you deal with in the future, objects that could "plug in" more easily, if you could go ahead and establish a suitable interface now?

You can add such interfaces to improve model understanding now-and point to change flexibility for the future (hey, this might even get you a pay raise). And you can demonstrate to your customer that your model is ready for expansion---just send more money!

Factoring-out for the future for Zoe's Zones

Take a look at a zone and its sensors.

Figure 3-. A zone and its sensors

Factor-out common method signatures into a new interface.

Figure 3-. Factoring-out a common interface

Now adjust the object model, so a zone holds a collection of IActivate objects.

Figure 3-. A zone and its collection of IActivates

And go even further: an IActivate object consists of other IActivates:

Figure 3-. An IActivate and its collection of IActivates

Yet that is going a bit too far. An IActivate is an interface; it has no attributes, it has no object connections.

So showing an object connection with a constraint on an interface-really is going a bit too far. We cannot require an interface to implement an object connection.

Now, what we can do is use method naming conventions that imply attributes and services:

-get/set method signatures imply attributes

getStatus and setStatus

-add/remove method signatures which imply object connections

addIActivate and removeIActivate.

Using the add/remove naming convention, we end up with a new, improved IActivate interface:

Figure 3-. An IActivate-and adding/removing IActivates

Here's a corresponding scenario view, showing add, activate, and deactivate:

Figure 3-. An IActivate interacting with its IActivates

In Java, it looks like this:

public interface IActivate {

void activate();

void deactivate(); }

public interface IActivateGroup extends IActivate {

void addIActivate(IActivate anIActivate);

void removeIActivate(IActivate anIActivate); }

public class Sensor extends Object implements IActivate {

#

// methods / public / IActivate implementation

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

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

#

}

public class Zone extends Object implements IActivateGroup {

#

// attributes / private / object connections

private Vector activates = new Vector()

// methods / public / IActivateGroup implementation

public addIActivate(IActivate anIActivate) {

this.activates.addElement(anIActivate); }

public removeIActivate(IActivate anIActivate) {

this.activates.removeElement(anIActivate); }

public void activate() {

// iterate through the vector of "activates" and tell each iActivate to activate

Enumeration activateList = this.activate.elements();

while (activateList.hasMoreElements()) {

// must cast the element to IActivate

IActivate anIActivate = (IActivate)activateList.nextElement();

anIActivate.activate(); }

}

public void deactivate() {

// iterate through the vector of "activates" and tell each iActivate to deactivate

Enumeration activatableList = this.activate.elements();

while (activateList.hasMoreElements()) {

// must cast the element to IActivate

IActivate anIActivate = (IActivate)activatableList.nextElement();

anIActivate.deactivate(); }

}

#

}

Flexibility, extensibility, pluggability for Zoe's Zones

One aspect of flexibility, extensibility, and pluggability is being able to combine objects that you are already working with in new ways-combinations that you might not have anticipated at first.

Now a zone could be a collection of other zones, and those zones could be a collection of sensors. And a sensor could be a collection of other sensors. Nice.

A sensor could be a collection of zones-yet this would probably not make much sense. Interfaces allow us to express what kind of behavior must be supported. Reasonableness applies when it comes to deciding what to plug together, though!

Another aspect of extensibility is being able to add in new classes of objects: ones that you can anticipate now; and ones that may surprise you in the future.

Look at the interfaces that you are establishing and consider: what other classes of objects might implement that same interface at some point in the future?

For zones and sensors, we might look ahead to additional IActivates: switches, motors, conveyor belts, and robot arms.

Figure 3-. Adding in some new IActivates-flexibility, extensibility, pluggabililty

Just add the new classes to the object model.

The scenario view stays exactly the same as before-no change required.

Summary

In this chapter, you've worked with interfaces, common sets of method signatures that you define for use again and again in your application.

Designing with interfaces is the most significant aspect of Java-inspired design: freedom from object connections that are hardwired to just one class of objects, freedom from scenario interactions that are hardwired to just one class of objects. For systems in which flexibility, extensibility, and pluggability are key issues, Java-style interfaces are a must. Indeed, the larger the system, and the longer the potential life span of a system, the more significant interface-centric scenario development becomes.

In this chapter, you've learned and applied these specific strategies for designing better apps:

"Challenge Each Object Connection" Strategy: Is this connection hard-wired to objects in that class (simpler)? Or is this a connection to any object which implements a certain interface (more flexible, extensible, and pluggable)?

"Challenge Each Message-Send" Strategy: Is this message-send hard-wired to object in that class (simpler)? Or is this a message-send to any object that implements a certain interface (more flexible, extensible, and pluggable)?

"Factor-Out Repeaters" Strategy: Factor-out method signatures that repeat within your object model. Resolve synonyms into a single signature. Generalize overly-specific names into a single signature. Reasons: explicitly capture that commonality; bring a higher level of abstraction into the model.

"Factor-Out to a Proxy" Strategy: Factor-out method signatures into a proxy, an object with a solo connection to some other object. Reasons: simplifies the proxy within an object model and its scenario views.

"Factor-Out for Analogous Apps" Strategy: Factor-out method signatures that could be applicable in analogous apps. Reason: increase likelihood of using and reusing off-the-shelf classes.

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 2
Source http://www.oi.com/3.htm
On to Chapter 4

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