Information Hiding and ADTs

Steven Zeil

Last modified: Jul 04, 2014

Contents:
1. Abstraction
2. Abstract Data Types
2.1 ADT Members
2.2 The ADT as Contract
3. ADT Implementations
4. Where Do ADTs Come From?

Programs are Layered

Large programs are built in layers.

1. Abstraction


Abstraction

Abstraction is a creative process of focusing attention on the main problems by ignoring lower-level details.

  1. procedural abstraction and

  2. data 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);


Data Abstraction

A data abstraction is a mental model of what can be done to a collection of data.


Example: times

A time denotes a given moment within a day, to the nearest second.


Example: ordered collection

An ordered collection is a sequence of data in which each element is smaller than the ones that follow it.

2. Abstract Data Types


ADTs

An abstract data type (ADT) is a type name and a set of members belonging to that type

2.1 ADT Members


ADT Members

The members of an ADT can be divided into two kinds:


Example: Time

Here is a possible ADT for our earlier abstraction of “Time”:

It is expressed in three parts: \ePicOnRight

  1. ADT name

  2. Attributes

  3. operations (function members that do things other than just access attributes)


Example: Ordered Collection

And here is a similar description, this time of an ADT for our abstraction of an ordered collection. \ePicOnRight

2.2 The ADT as Contract


The Contract

When an ADT specification/implementation is provided to users (application programmers):


Why the Contract?

What do we gain by holding ourselves to this contract?

3. ADT Implementations


ADT Implementations

We go from ADT to ADT implementation by adding specific data structures and algorithms.

In C++, this is generally done using a C++ class or struct.


Example: Time implementation

  As ADT designer, I might consider two possible data structures:

time.h

time1.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


struct Time {
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  int getMinutes();
  int getSeconds();

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


  /**
   * Print a time in the format HH:MM:SS (two digits each)
   */
  void print (std::ostream& out);

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);

  // From here on is hidden
  int hours;
  int minutes;
  int seconds;

};

#endif // TIMES_H

time.cpp

time1.cpp
#include "time1.h"

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */



  // Create time objects
Time::Time() // midnight
{
  hours = minutes = seconds = 0;
}


Time::Time (int h, int m, int s)
{
  hours = h;
  minutes = m;
  seconds = s;
}

  // Access to attributes
int Time::getHours()
{
  return hours;
}

int Time::getMinutes()
{
  return minutes;
}

int Time::getSeconds()
{
  return seconds;
}

// Calculations with time
void Time::add (Time delta)
{
  hours += delta.hours;
  minutes += delta.minutes;
  seconds += delta.seconds;

  minutes += seconds / 60;
  seconds = seconds % 60;

  hours += minutes / 60;
  minutes = minutes % 60;
}
      

Time Time::difference (Time fromTime)
{
  Time diff (hours - fromTime.hours, 
         minutes - fromTime.minutes, 
         seconds - fromTime.seconds);
  while (diff.seconds < 0)
    {
      diff.seconds += 60;
      --diff.minutes;
    }
  while (diff.minutes < 0)
    {
      diff.minutes += 60;
      --diff.hours;
    }
}
  
  
/**
 * Read a time (in the format HH:MM:SS) after skipping any
 * prior whitepace.
 */
void Time::read (std::istream& in)
{
  char c;
  in >> hours >> c >> minutes >> c >> seconds;
}


/**
 * Print a time in the format HH:MM:SS (two digits each)
 */
void Time::print (std::ostream& out)
{
  out << hours << ':' << minutes << ':' << seconds;
}

/**
 *  Compare two times. Return true iff time1 is earlier than or equal to
 *  time2
 */
bool Time::noLaterThan(const Time& time2)
{
  // First check the hours
  if (hours > time2.hours)
    return false;
  if (hours < time2.hours)
    return true;
  // If hours are the same, compare the minutes
  if (minutes > time2.minutes)
    return false;
  if (minutes < time2.minutes)
    return true;
  // If hours and minutes are the same, compare the seconds
  if (seconds > time2.seconds)
    return false;
  return true;
}



/**
 *  Compare two times. Return true iff time1 is equal to
 *  time2
 *
 */
bool Time::equalTo(const Time& time2)
{
  return hours == time2.hours
    && minutes == time2.minutes
    && seconds == time2.seconds;
}

time.h

time2.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


struct Time {
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  int getMinutes();
  int getSeconds();

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


  /**
   * Print a time in the format HH:MM:SS (two digits each)
   */
  void print (std::ostream& out);

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);

  // From here on is hidden
  int secondsSinceMidnight;
};

#endif // TIMES_H

time.cpp

time2.cpp
#include "time2.h"

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */



  // Create time objects
Time::Time() // midnight
{
  secondsSinceMidnight = 0;
}


Time::Time (int h, int m, int s)
{
  secondsSinceMidnight = s + 60 * m + 3600*h;
}

  // Access to attributes
int Time::getHours()
{
  return secondsSinceMidnight / 3600;
}

int Time::getMinutes()
{
  return (secondsSinceMidnight % 3600) / 60;
}

int Time::getSeconds()
{
  return secondsSinceMidnight % 60;
}

// Calculations with time
void Time::add (Time delta)
{
  secondsSinceMidnight += delta.secondsSinceMidnight;
}
      

Time Time::difference (Time fromTime)
{
  Time diff;
  diff.secondsSinceMidnight = 
    secondsSinceMidnight - fromTime.secondsSinceMidnight;
}
  
  
/**
 * Read a time (in the format HH:MM:SS) after skipping any
 * prior whitepace.
 */
void Time::read (std::istream& in)
{
  char c;
  int hours, minutes, seconds;
  in >> hours >> c >> minutes >> c >> seconds;
  Time t (hours, minutes, seconds);
  secondsSinceMidnight = t.secondsSinceMidnight;
}


/**
 * Print a time in the format HH:MM:SS (two digits each)
 */
void Time::print (std::ostream& out)
{
  out << getHours() << ':' << getMinutes() << ':' << getSeconds();
}

/**
 *  Compare two times. Return true iff time1 is earlier than or equal to
 *  time2
 */
bool Time::noLaterThan(const Time& time2)
{
  return secondsSinceMidnight <= time2.secondsSinceMidnight;
}



/**
 *  Compare two times. Return true iff time1 is equal to
 *  time2
 *
 */
bool Time::equalTo(const Time& time2)
{
  return secondsSinceMidnight == time2.secondsSinceMidnight;
}

Each approach has pro’s and cons. The first will be faster if we are mainly reading and writing times. The second will be faster if we are doing lots of calculations involving times.


Switching Implementations


Example of an ADT Implementation

(This really does show data members in the middle- the +/- markers are the giveaway.)

We get that flexibility by concentrating on operations that we want to keep public, and hiding the data structures used to provide those operations.

We’ll explore this aspect of ADT design in more detail later. \ePicOnRight

4. Where Do ADTs Come From?


Where Do ADTs Come From?

ADT’s may be


Real-World Objects make Good ADTs

Domain and application-specific ADTs generally reflect the real-world objects found in the application domain


The OO Philosophy

This is sometimes expressed as The Object-Oriented Philosophy


Example: Library Holdings

A question to think about:

What ADT’s would you expect in a system to manage a library’s inventory?

Answer