ADTs

Steven J Zeil

1. Abstraction

Abstraction

In general, abstraction is a creative process of focusing attention on the main problems by ignoring lower-level details.

In programming, we encounter two particular kinds of abstraction:

1.1 Procedural Abstraction

Procedural Abstraction

A procedural abstraction is a mental model of what we want a subprogram to do (but not how to do it).

Example:

double hypotenuse = sqrt(side1*side1 + side2*side2);

We can write this, understanding that the sqrt function is supposed to compute a square root, even if we have no idea how that square root actually gets computed.

1.2 Data Abstraction

Data Abstraction

A data abstraction is a mental model of what can be done to a collection of data. It deliberately excludes details of how to do it.

Example: calendar days

A day (date?) in a calendar denotes a 24-hour period, identified by a specific year, month, and day number.

That’s it. That’s probably all you need to know for you and I to agree that we are talking about a common idea.

Example: cell names

Every cell in a spreadsheet has a unique name. The name has a column part and a row part.

Example: a book

How to describe a book?

Example: positions within a container

Many of the abstractions that we work with are “containers” of arbitrary numbers of pieces of other data.

Any time you have an ordered sequence of data, you can imagine the need to look through it. That then leads to the concept of a position within that sequence, with notions like

2. Abstract Data Types

Adding Interfaces

Definition of an Abstract Data Type

(traditional): An abstract data type (ADT) is a type name and a list of operations on that type.

It’s convenient, for the purpose of this course, to modify this definition just slightly:

Definition (alternate): An abstract data type (ADT) is a type name and a list of members (data or function) on that type.

ADT Members: attributes and operations

Commonly divided into

2.1 Examples

Calendar Days

Nothing in the definition of ADT that says that the interface has to be written out in a programming language.

UML diagrams present classes as a 3-part box: name, attributes, & operations

Calendar Days: alternative

But we can use a more programming-style interface:

class Day {
public:
   // Attributes
   int getDay();
   void setDay (int);
   int getMonth();
   void setMonth(int);
   int getYear();
   void setYear(int);
   
   // Operations
   Day operator+ (int numDays);
   int operator- (Day);
   bool operator< (Day);
   bool operator== (Day);
     ⋮

See also the interface developed in sections 3.1 and 3.2 of your text (Horstmann).

Notations

class Day {
public:
   // Attributes
   int getDay();
   void setDay (int);
   int getMonth();
   void setMonth(int);
   int getYear();
   void setYear(int);
   
   // Operations
   Day operator+ (int numDays);
   int operator- (Day);
   bool operator< (Day);
   bool operator== (Day);
     ⋮

Cell Names

Here is a possible interface for our cell name abstraction.

cellnameInterface.h

Arguably, the diagram presents much the same information as the code

Example: a book

If we were to try to capture our book abstraction (concentrating on the metadata), we might come up with something like:

bookAbstraction0.h

Example: positions within a container

Coming up with a good interface for our position abstraction is a problem that has challenged many an ADT designer.

bookNumericPositions.h

Iterators

The solution adapted by the C++ community is to have every ADT that is a “container” of sequences of other data to provide a special type for positions within that sequence.

A Possible Position Interface

In theory, we could satisfy this requirement with an ADT like this:

authorPosition0.h

which in turn would allow us to access authors like this:

void listAllAuthors(Book& b)
{
   for (AuthorPosition p = b.begin(); p != b.end(); 
        p = p.next())
     cout << "author: " << p.getData() << endl;
}

The Iterator ADT

For historical reasons (and brevity), however, C++ programmers use overloaded operators for the getData() and next() operations:

authorPosition1.h

so that code to access authors would look like this:

void listAllAuthors(Book& b)
{
   for (AuthorPosition p = b.begin(); p != b.end(); 
        ++p)
     cout << "author: " << *p << endl;
}

This ADT for positions is called an iterator (because it lets us iterate over a collection of data).

2.2 Design Patterns

Iterator as a Design Pattern

The idea of an iterator is an instance of what we call a design pattern:

Pattern, not ADT

In C++, our application code does not actually work with an actual ADT named “Iterator”.

Realizing a Design Pattern

3. ADTs as contracts

ADTs as contracts

An ADT represents a contract between the ADT developer and the users (application programmers).

The Contract

Why the Contract

What do we gain by holding ourselves to this contract?

3.1 Information Hiding

Information Hiding

Every design can be viewed as a collection of “design decisions”.

Encapsulation

Although ADTs can be designed without language support, they rely on programmers’ self-discipline for enforcement of information hiding.

Encapsulation is the enforcement of information hiding by programming language constructs.

4. ADT Implementations

ADT Implementations

An ADT is implemented by supplying

We sometimes refer to the ADT itself as the \firstterm{ADT specification} or the ADT interface, to distinguish it from the code of the \firstterm{ADT implementation}.

In C++, implementation is generally done using a C++ class.

4.1 Examples

Calendar Day Implementations

As ADT designer, I might consider two possible data structures: * store the month, day, & year as integers:

v1-day.h

v1-day.cpp

v2-day.h

v2-day.cpp

Each approach has pro’s and cons.

CellName implementation

cellnameImpl.cpp

There are some options here the have not been explored:

Book implementation

We can implement Book in book.h:

book1.h

and in book.cpp:

book1.cpp

We’ll explore some of the details and alternatives of these implementations in the next lesson.