Practical Object Oriented Design in Ruby: Interfaces
It's tempting to think of object oriented applications as being the sum of their classes. Classes are so very visible; design discussions often revolve around class responsibilities and dependencies. Classes are what you see in your text editor and what you check in to your source code repository.
There is design detail that must be captured at this level but an object oriented application is more than just classes. It is made up of classes but defined by messages. Classes control what's in your source code repository; messages reflect the living, animated, application.
Design, therefore, must be concerned with the messages that pass between objects. It deals not only with what objects know (their responsibilities) and who they know (their dependencies), but how they talk to one another. The conversation between objects takes place using their interfaces; this chapter explores creating flexible interfaces that allow applications to grow and change.
Understanding Interfaces
Imagine two running applications, as illustrated in figure 4.1. Each consists of objects and the messages that pass between them.
In the first application, the messages have no apparent pattern. Every object may send any message to any other object. If the messages left visible trails, these trails would eventually draw a woven mat, with each object connected to every other.
In the second application, the messages have a clearly defined pattern. Here the objects communicate in specific and well defined ways. If these messages left trails, the trails would accumulate to create a set of islands with occasional bridges between them.
Figure 4.1 Communication patterns
Both applications, for better or worse, are characterized by the patterns of their messages.
The objects in the first application are difficult to reuse. Each one exposes too much of itself and knows too much about its neighbors. This excess knowledge results in objects that are finely, explicitly and disastrously tuned to do only the things that they do right now. No object stands alone; to reuse any you need all, to change one thing you must change everything.
The second application is composed of plug-able, component-like objects. Each reveals as little about itself, and knows as little about others, as possible.
The design issue in the first application is not necessarily a failure of dependency injection or single responsibility. Those techniques, while necessary, are not enough to prevent the construction of an application whose design causes you pain. The roots of this new problem lie not in what each class does but with what it reveals. In the first application each class reveals all. Every method in any class is fair game to be invoked by any other object.
Experience tells you that all the methods in a class are not the same; some are more general or more likely to change than others. The first application takes no notice of this. It allows all methods of any object, regardless of their granularity, to be invoked by others.
In the second, the message patterns are visibly constrained. This application has some agreement, some bargain, about which messages may pass between its objects. Each object has a clearly defined set of methods that it expects others to use.
These exposed methods comprise the class's public interface.
The word interface can refer to a number of different concepts. Here the term is used to refer to the kind of interface that is within a class. Classes implement methods, some of those methods are intended to be used by others and these methods make up its public interface.
An alternative kind of interface is one that spans across classes, that is independent of any single class. Used in this sense, the word interface represents a set of messages where the messages themselves define the interface. Many different classes may, as part of their whole, implement the methods that the interface requires. It's almost as if the interface defines a 'virtual' class; any class which implements the required methods can act like the 'interface' kind of thing.
The remainder of this chapter will talk about the first kind of interface, i.e., methods within a class, and how and what to expose to others. Chapter 5, Ducks, will talk about the second kind of interface, the one that represents a concept that is broader than a class and is defined by a set of messages.