Pre-OO Design: Focus on Functions

Steven J Zeil

Last modified: August 20, 2013

This course is concerned with object-oriented programming (OOP), design (OOD), and analysis (OOA). Clearly, with so much of this OO stuff flying around, it would be nice if we had an idea of what an object is, and just what is meant by “object-oriented”.

1 Observations on Pre-OO Programming Languages

1.1 The Roots of OOP

OO programming and design have their roots in simulation. In the late 1960’s, the population of programmers was dominated by people who had switched fields, typically from the mathematics or physical sciences, rather than people who had originally trained in computer science. Indeed, academic programs in computer science were far less common than they are today, and tended to treat CS as more of a tool to be applied to application areas rather than as a field of study in its own right. Commonly, a CS degree program would offer two tracks - business programming and scientific programming.

One of the more active and exciting application areas at the time was simulation. Programmers in this area began to recognize common patterns arising over and over in their code. A number of code libraries were designed and distributed that attempted to capture and simplify these common simulation tasks. Eventually, special-purpose simulation programming languages were developed.

There was ample precedence for this kind of specialization. The dominant languages of the time were generally perceived as tailored to specific applications: FORTRAN and APL for science applications, COBOL for business, LISP for A.I., SNOBOL for text/string processing, etc. Any of these languages could have been used for other kinds of applications, but it was certainly easier to work with a language that matched well with the application area.

Simula (1967) is now the best known of those special purpose simulation languages. In what seemed “natural” to someone writing simulations, Simula allowed a programmer to organize a simulation program in terms of a world of objects that interacted with one another via programmed behaviors. What could seem more natural to someone writing a simulation of some part of the real world?

1.2 Tension in Pre-OO Programming Languages

The history of programming languages and of design can be viewed as a continual contest against increasing complexity of software systems:

Problem: too many … Response Useful?
… statements nesting ( { … } ) OK
gather statements into functions OK
… functions nesting inadequate
gather functions into subsystems / “modules” Too loosely defined
gather functions into encapsulated ADTs Good
… ADTs Gather into namespaces/packages Not much help
Organize loosely into inheritance hierarchies The OO approach

Meanwhile, back in the “main stream” of programming language development, one of the main historic trends has always been trying to keep programs well organized even as programs get larger and larger.

The earliest and simplest way to cope with size was to allow individual statements to be grouped together into functions/procedures/subroutines (all effectively meaning the same thing). In fact this idea pre-dates high-level programming languages, being implicit in the machine code instruction sets of almost all CPUs ever built. It may even pre-date any actual programming. Some attribute this idea to Ada Lovelace’s programs for Babbage’s never-constructed analytical engine.

Functions allow us to cope when the number of individual statements in a program grows so large as to be unwieldy. So what do we do when the number of functions grows too large to be manageable? Programmers and designers tried to cope by grouping functions into “modules” or “subsystems”. These groupings were little more than a matter of documentation and physical arrangement of the functions within the source code files. On a team project, a single person who did not want to “play nice” could easily wreak havoc.

A more solid organizing principle arose in the notion of an Abstract Data Type (ADT). We’ll review this concept in more detail in a later lesson, but an ADT groups together a data type name and a collection of functions for manipulating data of that type.

ADTs were something of a revolution in programming, as they often led to remarkably clean, reusable modules. Of course, like all modules, the ADT originally was implemented simply by documenting it as such: “the functions in this file provide the ADT Stack…” An uncooperative team member could often compromise this organization (usually giving plausible but short-sighted excuses such as “I could get the program done faster if I bypassed that” or “It runs a millisecond faster if I access that bit of data directly instead of using those functions”).

The value of ADTs became widely accepted, and programming language designers responded by adding support for them into new programming languages. Languages such as Modula 2 (1970) and Ada (1983) added language mechanisms (called a “module” and a “package”, respectively) for grouping types and functions together. Both enforced “information hiding” by encapsulating portions of an ADT implementation that other programmers would then be unable to access without incurring compilation errors.

Many writers incorrectly regard ADTs, encapsulation, and information hiding as innovations of OOP, but in fact these ideas had been around for some time earlier, and were in widespread use before object-oriented programming languages (OOPL) made their entrance.

Indeed, we will eventually see that the vast majority of code written by even the very best programmers working in an OOPL is not particularly OO, but will exploit encapsulated ADTs very heavily.

2 Pre-OO Design Techniques

Observations

OK, let’s agree that a good ADT is a wonderful thing. Just where do good ADTs come from? Or, to put it another way, when you are designing a program, how do you figure out which ADTs you should use?

If you search the web for the term “abstract data type”, you will find lots of examples of things you might pull out of a library of data structures: stacks, queues, lists, etc. It’s easy to get the impression that an ADT is something that someone else provides for you as a convenient little package of code.

But that misses the point that “abstract data type” is an organizing principle for your own programs. ADTs for general purpose data structures are all very nice, but how do you come up with the special-purpose ADTs that organize your program in a way that is specific to the problem it solves?

Pre-OO design techniques were oriented towards functions performed by the program. For example, if you were working on automating the management of book handling in a typical library, you might start by dividing the system into major subsystems such as RecordStorage, CheckInOut, AccountMgmt, and InventoryMgmt. Under each subsystem, you would group functions to be performed, such as CheckOutBook, CheckInBook, and RenewBook under the CheckInOut subsystem, or addBook, removeBook, and transferToBranch under InventoryMgmt.

2.1 Stepwise Refinement & Top-Down Design

2.1.1 Stepwise Refinement Example

Automating book handling in a library:

If pre-OO designers were asked to design one of these functions, say, addBook, they would probably do so by “stepwise refinement”, a.k.a. “top-down design”.


Getting Started

Suppose we wanted to design the function to add a new book to the library’s collection.

function addBook (book, branchName)
{
  bookInfo = getBookInfo(book);
  record branchName as location in bookInfo;
  addToCardCatalog (bookInfo);
  addToInventory (bookInfo, branchName);
}

2.1.2 Refine One Thing

Next the designers would pick one of those rather vaguely understood lines of code in that function body and expand it, either in place or as a separate function. For example:

function addBook (book, branchName)
{
  bookInfo = getBookInfo(book);
  record branchName as location in bookInfo;
  // addToCardCatalog (bookInfo)
  add to AuthorIndex (bookInfo);
  add to SubjectIndex (bookInfo);
  add to TitleIndex (bookInfo);
  cardCatalog.addToAuthorIndex
  addToInventory (bookInfo, branchName);
}

The crucial idea here is that these new lines, taken together, add up to a method of achieving this line, the one we chose to expand.

We are not simply adding code. We are refining or expanding an abstract step into a series of somewhat more detailed ones.

If you, or another reader, do not believe that these lines add up to a procedure for achieving this line, then its time to stop and gp back. You’re doing something wrong.

2.1.3 Repeat

Moving on, we would againpick a vaguely understood line and expand it.

function addBook (book, branchName)
{
  bookInfo = getBookInfo(book);
  record branchName as location in bookInfo;
  // addToCardCatalog (bookInfo)
  //   add to AuthorIndex (bookInfo);
  authorList = bookInfo.getAuthors();
  for each author au in authorList {
    catalog.authorIndex.addByAuthor (au, bookInfo);
  }
  add to SubjectIndex (bookInfo);
  add to TitleIndex (bookInfo);
  cardCatalog.addToAuthorIndex
  addToInventory (bookInfo, branchName);
}

3 Where are All the ADTs?

Stepwise refinement was certainly not the only pre-OO design technique, though it was probably the best known and most widely used.

However, nearly all pre-OO design approaches focused on behavior and functionality and on how to break those down into smaller pieces.

And that remained a problem, because ADTs are not so much about breaking behavior down as they are about gathering related behaviors together.

Now, stepwise refinement is a valuable technique. It’s still probably the technique of choice for low-level design (the design of an algorithm for a single function). But it’s not nearly so useful as a technique for high-level design (the separation fo a large problem into separate modules). Consider that, at this point in our example, we have mentioned about a dozen different function names. None of them have been associated with an ADT. What are the odds that any of them would wind up inside an ADT? Pretty slim, usually.

An experienced designer might take note that the above process gives some hints as to some potential ADTs (Book, branchLibrary, CardCatalog, etc.). But this insight is pretty much separate from the design process itself. A designer has to be somewhat schizoid, actively participating in the design while simultaneously standing aside as a detached observer, watching for hints of possible ADTs.

When we move to Object Oriented Analysis and Design, our challenge will be to move the ADTs to the front of the process, to shift the focus away from functions and towards the data.

Does this men that we will never worry about designing single functions? No, of course not. ADTs have function members, and those will eventually need to be designed. But the emphasis will be on getting the ADTs first, and then starting on the design of the individual function members.

To put it another way,