Thinking in Objects, Page 2
The services presented to an end user comprise the interface. In the best case, only the services the end user needs are presented. Of course, which services the user needs might be a matter of opinion. If you put 10 people in a room and ask each of them to do an independent design, you might receive 10 totally different designs—and there is nothing wrong with that. However, as a rule of thumb, the interface to a class should contain only what the user needs to know. In the toaster example, the user only needs to know that the toaster must be plugged into the interface—which in this case is the electrical outlet.
Identifying the User
Perhaps the most important issue when designing a class is identifying the audience, or users, of the class.
The interface services' implementation details are hidden from the user. One goal regarding the implementation should be kept in mind: A change to the implementation should not require a change to the user's code. This might seem a bit confusing, but this goal is at the heart of the design issue. If the interface is designed properly, a change to the implementation should not require a change to the user's code. Remember that the interface includes the syntax to call a method and return a value. If this interface does not change, the user does not care whether the implementation is changed. As long as the programmer can use the same syntax and retrieve the same value, that's all that matters.
Recall that in the toaster example, although the interface is always the electric outlet, the implementation could change from a coal power plant to a nuclear power plant without affecting the toaster. There is one very important caveat to be made here: The coal or nuclear plant must also conform to the interface specification. If the coal plant produces AC power, but the nuclear plant produces DC power, there is a problem. The bottom line is that both the user and the implementation must conform to the interface specification.
An Interface/Implementation Example
Let's create a simple (if not very functional) Oracle database reader class. We'll write some Java code that will retrieve records from the Oracle database. As we've discussed, knowing your end users is always the most important issue when doing any kind of design. You should do some analysis of the situation and conduct interviews with end users, and then list the requirements for the project. The following are some requirements we might want to use for the database reader:
- We must be able to open a connection to the database.
- We must be able to close the connection to the database.
- We must be able to position the cursor on the first record in the database.
- We must be able to position the cursor on the last record in the database.
- We must be able to find the number of records in the database.
- We must be able to determine whether there are more records in the database (that is, if we are at the end).
- We must be able to position the cursor at a specific record by supplying the key.
- We must be able to retrieve a record by supplying a key.
- We must be able to get the next record, based on the position of the cursor.
With these requirements in mind, we can make an initial attempt to design the database reader class by creating possible interfaces for these end users.
In this case, the database reader class is intended for programmers who require use of a database. Thus, the interface is essentially the application programming interface (API) that the programmer will use. These methods are, in effect, wrappers that enclose the functionality provided by the database system. Why would we do this? We will explore this question in much greater detail later in the article; the short answer is that we might need to customize some database functionality. For example, we might need to process the objects so that we can write them to a relational database. Writing this middleware is not trivial as far as design and coding go, but it is a real-life example of wrapping functionality.
Figure 2 shows a class diagram representing a possible interface to the DataBaseReader class.
Figure 2: A Unified Modeling Language class diagram for the DataBaseReader class.
Note that the methods in this class are all public (remember that there are plus signs next to the names of methods that are public interfaces). Also note that only the interface is represented; the implementation is not shown. Take a minute to determine whether this class diagram generally satisfies the requirements outlined earlier for the project. If you find out later that the diagram does not meet all the requirements, that's okay; remember that OO design is an iterative process, so you do not have to get it exactly right the first time.
Remember that if a method is public, a programmer can access it, and thus, it is considered part of the class interface. Do not confuse the term interface with the keyword interface used in Java and C#—this term is discussed later.
For each of the requirements we listed, we need a corresponding method that provides the functionality we want. Now you need to ask a few questions:
- To effectively use this class, do you as a programmer need to know anything else about it?
- Do you need to know how the Oracle code actually opens the Oracle database?
- Do you need to know how the Oracle code physically positions itself over a specific record?
- Do you need to know how the Oracle code determines whether there are any more records left?
On all counts, the answer is a resounding no! You don't need to know any of this information. All you care about is that you get the proper return values and that the operations are performed correctly. In fact, the application programmer will most likely be at least one more abstract level away from the implementation. The application will use your classes to open the database, which in turn will invoke the proper Oracle API.
Although perhaps extreme, one way to determine the minimalist interface is to initially provide the user no public interfaces. Of course, the class will be useless; however, this forces the user to come back to you and say, "Hey, I need this functionality." Thus, you add interfaces only when it is requested. Never assume that the user needs something.