Java: Design and Modeling Opportunities


Desmond D'Souza

1 Introduction

Hi again! It is good to be back as a columnist in JOOP, this time on a different and exciting topic: Design with and for Java. I would also like to thank all the readers who provided me with many stimulating questions and excellent feedback on the many topics I had discussed over the last 3 years in the Education and Training column. Such feedback is always welcome, either through writing to the magazine editor, Dr. Richard Wiener, or directly to me.

So, what's with the Java buzz, anyway? Anyone who ventures into cyberspace for the first time is invariably somewhat awed by the magnitude and implications of what is happening out there. While the Web transformed the face of the Internet in the early 1990's and linked together its vast resources in an easily accessible way, today Java is poised to take the Web a significant step forward into the world of fully interactive Web applications, and into the world of distributed objects. However, Java is poised to be much more than a Internet programming language: it is an elegant, consistent, and surprisingly effective general-purpose object-oriented programming language as well, suited to both conventional as well as concurrent and distributed applications.

In this column we will examine Java and related technologies from a variety of perspectives, including design, specifications and models, concurrency, the Web, distributed objects, databases, and ActiveX. In short, this is not going to be a purely programming language column.

We will begin in this month's column with examining a specific Java language feature: the interface. Specifically, we will discuss what interfaces represent, and the opportunity they present for design and modeling to define behavioral types. We will then discuss in more detail (continuing in subsequent columns) the relationship between class and interface, and introduce the notion of refinement for effectively describing this relationship. Lastly, we will discuss the limitations of refinement based solely on sub-typing, and introduce a very expressive form of refinement adapted from the Catalysis method [3].

2 The Java "Interface"

One of the most serious errors made in many common object-oriented languages is the failure to distinguish type from class i.e. interface vs. implementation. The same, unfortunately, was true of some leading methodologies. Perhaps the implementation language constructs in Java will help improve popular analysis and modeling methods.

C++: sub-classes inherit both implementation and interface. While it is possible to inherit implementation without interface by hiding the interface behind private inheritance, it is not possible to do the opposite: conform to an interface without using its implementation. The compiler only permits substitution of objects known at compile time to be instances of a class derived (interface + implementation) from another.
Smalltalk: sub-classes inherit implementation from superclasses. Each class can support multiple protocols independently of the inheritance structure. The language only checks at run-time to ensure that the interface required by a client is actually supported by an object, by looking up each message send dynamically. Some programmers informally use the protocol facility to organize interfaces.
Objective-C: sub-classes inherit interface and implementation from superclass. There is, in addition, an explicit facility for protocols, very analogous to Java's interface.
Eiffel: despite supporting semantic specification of classes by pre/post-conditions and invariants, Eiffel also fails to make the distinction between type and class.

The Java interface [1] is used to define the interface a client can expect from an object, regardless of what class and superclass structure is used to implement that object. Any number of classes can independently provide implementations for an interface.

interface List {
	void addItem (Item toAdd);
	void addItem (Item toAdd, int position);
	Item delItem (int position);
}

Classes define implementations and provide interfaces.

class LinkedList implements List {
	public void addItem (Item toAdd);
	public void addItem (Item toAdd, int position) { ...... }
	public Item delItem (int position) { ...... }
	
	private List_Node first;
}

Any number of classes can implement the same interface, and a class can implement any number of interfaces. For example, suppose we had another interface Selector, which provided the ability to select a single item at a time (e.g. for a mouse-clickable list-box).

interface Selector {
	void select (Item toSelect);
	void deSelect (Item toDeselect);
	void activate (Item toActivate);
	Item getSelectedItem();
}

We can then have a class which provided two interfaces (Figure 1), one to the user through the GUI, the other to the application or problem-domain objects.

class ListBox implements Selector, List {
	// ----- Selector operations
	public void select (Item toSelect) { ....... }
	public void deSelect (Item toSelect) { ....... }
	public void activate (Item toActivate) { ...... }
	public Item getSelectedItem() { ..... }

	// ----- List operations
	public void addItem (Item toAdd) { ..... }
	public void addItem (Item toAdd, int position) { ....... }
	public Item delItem (int position) { ....... }

	// ----- private implementation
	private .....
}

A client should, at all times, only be aware of the interface of the objects it deals with, not their class. The only time where client code needs to name a class is when instantiating a new object. Even in that situation, careful design using factory-like design patterns will alleviate the dependencies a lot.

3 Client's View: Interfaces as Behaviors

An interface is very much a pure "type" i.e. it describes no more than what a client needs to know. Of course, simply looking at the signatures of a set of methods does not tell the client what those methods will do, and how they should be used (Figure 2). For example, where do items get added by default? How is position defined? Clearly, any valid implementation must conform to certain behavioral guarantees. The interface signatures themselves do not describe these behavioral guarantees.

If we try to describe the operations, we will find ourselves relying on an underlying vocabulary. For example:

addItem (i, p) spec { the item i has been added at position p and is selected }

Which immediately uncovers more questions and further underlying vocabulary: What are valid values for i, p? What about the previous item which was at position p? Proceeding along these lines, we try to find a common vocabulary which relates all the operations on this interface.

addItem (item, pos)
delItem (pos)

Clearly, this vocabulary has to be a part of the interface. It is not too useful to agree to a syntactic interface unless we also agree to the common vocabulary and behavior required of that interface, so that two different clients have the same understanding and expectation as an implementor of that interface.

We make these guarantees explicit by introducing a model for that interface. A model is a set of hypothesized queries which are used to define required behavior. This model has to be understood by a client, even if no functions actually implement them (they no not have to be invocable at run-time -- hence the term hypothesized queries). Taking a small liberty with Java syntax, we introduce this model as a part of the definition of the interface, with a visual modeling and design notation (Figure 4). Please note that the model is not an implementation.

This model is not an end in itself. The primary reason for building this model is to allow us to specify behaviors (see Behavior-driven vs. Data-driven Methodologies: A Non-Issue [2]). We can now define the behavior of our Listbox as a test which any correct implementation should satisfy, in terms of how those queries change as the result of an operation, and how any output values (or messages) relate to those queries (Figure 5). This contract is specified using the form: spec { preconditions :- postconditions }. Note that movedUp is an auxiliary model query; it has been introduced as a convenient way to defer a detailed description of moving items up.

Interfaces described with a clear model ("vocabulary") and behaviors are far easier to use than plain signature sets. But what is wrong with signature sets complemented with, say, comment blocks? Informal textual documentation of an interface should never go away. However, as soon as the vocabulary and operations start to become non-trivial, it becomes critical to have a more rigorous basis for the narrative. If not, the descriptions very quickly end up being inconsistent and ambiguous.

4 Implementors View: the "How"

If I provide my own implementation of this interface, how do I make use of the behaviors specified in the interface? An implementation can choose any representation it needs, and can implement operations against that representation. However, any valid implementation must support the abstract vocabulary defined by the model in the interface. Thus, every model query should be definable (i.e. mapped to) in terms of the actual representation chosen. The implemented operation should then map to the behavior specified in the interface.

Here we implement our interface using two data members:

class ListBoxA implements ListBox {
	// ----- Private data
	List items;
	Item selection;

	// ----- ListBox operations
	addItem (Item toAdd, int position) {
		items.insertAt (item, position);
		selection = item;
	}
}

Our implementation must map to the queries defined in the interface. Such a mapping is called a retrieval, as it "retrieves" the abstract concepts (in this case, queries) from a specific implementation of those concepts (in this case, the selected data representation). Figure 6 illustrates the implements relation, and how the corresponding retrieval might be presented in a tool (together with narrative text, as always).

This approach to relating interfaces to implementations is very attractive because it combines:

Precise statement of behavior in terms of an abstract model
    1. No unnecessary constraints on alternative implementations of that behavior

5 Terminology and Summary

Here are some definitions from Catalysis which apply to our discussion:

Type: a (specification of a) set of objects (implemented through any mechanisms, including classes, sub-classes, etc.) which exhibit a common behavior (as specified in the specification of that type)
Type-model: a hypothesized set of queries which define an abstract view of state (and, in advanced usage, state changes) of all objects of that type.
Refinement: a relationship between abstract and concrete descriptions. It also defined how the abstract view is provided by the concrete model, using a retrieval. When a class implements an interface we are applying one kind of refinement with a corresponding retrieval. Catalysis is based on a powerful set of refinements.

Next time we will see how type and sub-types accommodate multiple interfaces, such as our List and Selector interfaces. We will see how conceiving generic queries for models, while it demands good abstraction skills, also provides very expressive capabilities. We will then proceed to discuss some very specific reasons for using sub-types to simplify client dependencies.

6 References

[1] Java Language Reference Manual

[2] Behavior-driven vs. Data-driven Methodologies: A non-Issue? http://www.iconcomp.com/papers

[3] Catalysis: Practical Rigor and Refinement: http://www.iconcomp.com/papers; Prentice Hall, forthcoming


Copyright 1994-1996 ICON Computing Inc.

For further information, questions, or to report problems: webmaster@iconcomp.com

Join our notification mailing list to learn about important changes to our web site.