• No results found

1.3.5 Minimal and Complete Interfaces

The interface for a class is made up of the publicly accessible methods and data. Typically, a class interface contains only methods, as you will see, because publicly accessible data fi elds generally

Abstract Data Types 11

cause problems. The interface for a class describes the only way for programmers to interact with that class. Thus, interfaces are where collaboration between objects takes place. It is through its interface that a class is coupled to other classes. Designing good class interfaces is an important skill.

Each class should be easy to understand. Thus, when designing a class, you should keep the number of its methods small. However, your classes should provide programmers the power to do what they need to do easily. These desires are at odds.

A complete interface for a class is one that will allow programmers to accomplish any reasona- ble task, given the responsibilities of that class. A minimal interface for a class is one that contains a method if and only if that method is essential to that class’s responsibilities. Programmers can more easily learn how to interact with a class that has a minimal interface, as it has fewer methods to under- stand. Classes with minimal interfaces are also much easier to maintain. Changes to a class with an extensive interface could affect many of its methods.

You should evaluate classes in terms of how complete and minimal their interfaces are. It is important that interfaces be complete. Of somewhat less importance is having a minimal interface; sometimes a nonessential method is just too useful to omit.

The interface to a function or method is more accurately called its signature . The signature con- sists of a function’s name; the number, order, and types of its arguments; and any qualifi ers such as const that might apply. A signature looks very much like the function’s prototype, but does not include its return type. You use the interface to a function or method when you call it, sending it the correct number, order, and type of arguments.

1.4

Abstract Data Types

Often the solution to a problem requires operations on data. Such operations are broadly described in one of three ways:

Add data to a data collection.

Remove data from a data collection.

Ask questions about the data in a data collection.

FIGURE 1-3 A revised implementation communicates through the same slit in the wall

Data sorted into ascending order Unorganized data new!

improved faster, smaller sort function

(sorts into ascending order)

MyProgram

(does interesting things) Sort his data for me;

I don't care how you do it

Typical operations on data

The details of the operations, of course, vary from application to application, but the overall theme is the management of data. Realize, however, that not every problem uses or requires all of these operations.

Most of this book is about data abstraction. To enable you to think abstractly about data, you should defi ne an abstract data type , or ADT . An ADT is a collection of data and a set of operations on the data. You can use an ADT’s operations, if you know their specifi cations, without knowing how the operations are implemented or how the data is stored.

Ultimately, someone—perhaps you—will implement the ADT by using a data structure , which is a construct that you can defi ne within a programming language to store a collection of data. For exam- ple, you might store the data in a C++ array of strings or in an array of objects or in an array of arrays.

For example, suppose that you need to store a collection of names in a manner that allows you to search rapidly for a given name. A collection of name items providing for rapid searches is the descrip- tion of a simple ADT. The description of an ADT’s operations must be rigorous enough to specify completely their effect on the data, yet must specify neither how to store the data nor how to carry out the operations. For example, the ADT operations should not specify whether to store the data in con- secutive memory locations or in disjoint memory locations. You choose a particular data structure when you implement an ADT.

When a program must perform data operations that are not directly supported by the language, you should fi rst design an ADT and carefully specify what the ADT operations are to do (the con- tract). Then —and only then— should you implement the operations with a data structure. If you implement the operations properly, the rest of the program will be able to assume that the operations perform as specifi ed—that is, that the terms of the contract are honored. However, the program must not depend on a particular technique for supporting the operations.

An ADT is not a fancy name for a data structure Specifi cations indicate what ADT operations do, but not how to implement them Carefully specify an ADT’s operations before you implement them

Note:

ADTs versus data structures

An abstract data type is a specifi cation for a group of values and the operations on those values.

A data structure is an implementation of an ADT within a programming language. To give you a better idea of the conceptual difference between an ADT and a data structure, con- sider a refrigerator’s ice dispenser, as Figure 1-4 illustrates. It has water as input and produces as out- put either chilled water, crushed ice, or ice cubes, according to which one of three buttons you push. ADTs and data

structures are not the same

FIGURE 1-4 A dispenser of chilled water, crushed ice, and ice cubes Chilled water Crushed ice Ice cubes No ice Water input

User view from specifications Technician view Chilled water maker Crushed ice maker

No ice indicator Ice cube

Abstract Data Types 13

It also has an indicator that lights when no ice is available. The water is analogous to data; the opera- tions are chill , crush , cube , and isEmpty (or noIce ). At this level of design, the dispenser is analogous to an ADT; you are not concerned with how the dispenser will perform its operations, only that it per- forms them. If you want crushed ice, do you really care how the dispenser accomplishes its task, as long as it does so correctly? Thus, after you have specifi ed the dispenser’s operations, you can design many uses for crushed ice without knowing how the dispenser accomplishes its tasks and without the distraction of engineering details.

Eventually, however, someone must build the dispenser. Exactly how will this machine produce crushed ice, for example? It could fi rst make ice cubes and then either crush them between two steel rollers or smash them into small pieces by using hammers. Many other techniques are possible. The internal structure of the dispenser corresponds to the implementation of the ADT in a programming language, that is, to a data structure.

Although the owner of the dispenser does not care about its inner workings, he or she does want a design that is as effi cient in its operation as possible. Similarly, the dispenser’s manufacturer wants a design that is as easy and inexpensive to build as possible. You should have these same concerns when you choose a data structure to implement an ADT. Even if you do not implement the ADT yourself, but instead use an already implemented ADT, you—like the person who buys a refrigerator—should care about at least the ADT’s effi ciency.

Notice that steel walls surround the dispenser. The only breaks in the walls accommodate the input (water) to the machine and its output (chilled water, crushed ice, or ice cubes). Thus, the machine’s interior mechanisms are not only hidden from the user, but also inaccessible. In addition, the mechanism of one operation is hidden from and inaccessible to another operation.

This modular design has benefi ts. For example, you can improve the operation crush by modify- ing its implementation without affecting the other modules. You could also add an operation to the machine without affecting the original three operations. Thus, both abstraction and information hid- ing are at work here.

To summarize, data abstraction results in a wall of ADT operations between data structures and the program that accesses the data within these data structures, as Figure 1-5 illustrates. If you are on

A program should not depend on the details of an ADT’s implementation

FIGURE 1-5 A wall of ADT operations isolates a data structure from the program that uses it

MyProgram

(does interesting things) Data structure

Add

Contains

the program’s side of the wall, you will see an interface that enables you to communicate with the data structure. That is, you ask the ADT operations to manipulate the data in the data structure, and they pass the results of these manipulations back to you.

This process is analogous to using a vending machine. You press buttons to communicate with the machine and obtain something in return. The machine’s external design dictates how you use it, much as an ADT’s specifi cations govern what its operations are and what they do. As long as you use a vending machine according to its design, you can ignore its inner technology. As long as you agree to access data only by using ADT operations, your program can be oblivious to any change in the data structures that implement the ADT.

The following pages describe several abstract data types, focusing on the specifi cations of their operations, but not their implementations.