[ Team LiB ] Previous Section Next Section

Interfaces and Abstract Classes

An abstract class is a class that cannot be directly instantiated. Instead, you instantiate an instance of a subclass. Typically, an abstract class has one or more operations that are abstract. An abstract operation has no implementation; it is pure declaration so that clients can bind to the abstract class.

The most common way to indicate an abstract class or operation in the UML is to italicize the name. You can also make properties abstract, indicating an abstract property or accessor methods. Italics are tricky to do on a whiteboards, so you can use the label: {abstract}.

An interface is a class that has no implementation; that is, all its features are abstract. Interfaces correspond directly to interfaces in C# and Java and are a common idiom in other typed languages. You mark an interface with the keyword «interface».

Classes have two kinds of relationships with interfaces: providing and requiring. A class provides an interface if it is substitutable for the interface. In Java and .NET, a class can do that by implementing the interface or implementing a subtype of the interface. In C++, you subclass the class that is the interface.

A class requires an interface if it needs an instance of that interface in order to work. Essentially, this is having a dependency on the interface.

Figure 5.6 shows these relationships in action, based on a few collection classes from Java. I might write an Order class that has a list of line items. Because I'm using a list, the Order class is dependent on the List interface. Let's assume that it uses the methods equals, add, and get. When the objects connect, the Order will actually use an instance of ArrayList but need not know that in order to use those three methods, as they are all part of the List interface.

Figure 5.6. A Java example of interfaces and an abstract class

graphics/05fig06.gif

The ArrayList itself is a subclass of the AbstractList class. AbstractList provides some, but not all, the implementation of the List behavior. In particular, the get method is abstract. As a result, ArrayList implements get but also overrides some of the other operations on AbstractList. In this case, it overrides add but is happy to inherit the implementation of equals.

Why don't I simply avoid this and have Order use ArrayList directly? By using the interface, I allow myself the advantage of making it easier to change implementations later on if I need to. Another implementation may provide performance improvements, some database interaction features, or other benefits. By programming to the interface rather than to the implementation, I avoid having to change all the code should I need a different implementation of List. You should always try to program to an interface like this; always use the most general type you can.

I should also point out a pragmatic wrinkle in this. When programmers use a collection like this, they usually initialize the collection with a declaration, like this:

private List lineItems = new ArrayList();

Note that this strictly introduces a dependency from Order to the concrete ArrayList. In theory, this is a problem, but people don't worry about it in practice. Because the type of lineItems is declared as List, no other part of the Order class is dependent on ArrayList. Should we change the implementation, there's only this one line of initialization code that we need to worry about. It's quite common to refer to a concrete class once during creation but to use only the interface afterward.

The full notation of Figure 5.6 is one way to notate interfaces. Figure 5.7 shows a more compact notation. The fact that ArrayList implements List and Collection is shown by having ball icons, often referred to as lollipops, out of it. The fact that Order requires a List interface is shown by the socket icon. The connection is nicely obvious.

Figure 5.7. Ball-and-socket notation

graphics/05fig07.gif

The UML has used the lollipop notation for a while, but the socket notation is new to UML 2. (I think it's my favorite notational addition.) You'll probably see older diagrams use the style of Figure 5.8, where a dependency stands in for the socket notation.

Figure 5.8. Older dependencies with lollipops

graphics/05fig08.gif

Any class is a mix of an interface and an implementation. Therefore, we may often see an object used through the interface of one of its superclasses. Strictly, it wouldn't be legal to use the lollipop notation for a superclass, as the superclass is a class, not a pure interface. But I bend these rules for clarity.

As well as on class diagrams, people have found lollipops useful elsewhere. One of the perennial problems with interaction diagrams is that they don't provide a very good visualization for polymorphic behavior. Although it's not normative usage, you can indicate this along the lines of Figure 5.9. Here, we can see that, although we have an instance of Salesman, which is used as such by the Bonus Calculator, the Pay Period object uses the Salesman only through its Employee interface. (You can do the same trick with communication diagrams.)

Figure 5.9. Using a lollipop to show polymorphism in a sequence diagram

graphics/05fig09.gif

    [ Team LiB ] Previous Section Next Section