The Object-Oriented Philosophy
Steven J Zeil
If OO analysis and design is about any one thing, it’s about how to find and recognize good ADTs.
Earlier, we looked at functions as a basis for design. We saw that, although certainly workable as a low-level design technique, designing based on functionality does not naturally lead to good ADT organization.
1 The Object-Oriented Philosophy
As we have noted, OO programming languages had their roots in simulation.
The first proponents of OO looked back to simulation, one of the early success stories in programming and design. Many early simulation programmers were able to construct programs that were organized in a way that made them easy to modify and maintain.
In part this was because Simula and other simulation programming languages offered features that supported ADTs as modular building blocks – but so did many other programming languages. Beyond the question of what the language supported, there was the issue of how those simulation programmers organized their programs in the first place. They were, in a very literal sense, keeping it real.
This, it was thought, was because the focus on modeling the real world, in a language that created a module for each kind of real-world object, led to programs that felt “natural” and easily understood by anyone who understood the part of the real world that was being simulated.
1.1 Program = Simulation
The Object-Oriented Philosophy
- Every program is really a simulation.
- The quality of a program’s design is proportional to the faithfulness with which the structures and interactions in the program mirror those in the real world.
According to this philosophy, a program to manage book handling in a library is really a simulation of what the librarians would have done prior to automation. A program to compute a company payroll is a simulation of what the accounting clerks would have done. A program to compute student grades at the end of a semester is a simulation of what a teacher would have done manually.
Now, sometimes the worlds being simulated may be artificial or imaginary (e.g., chess and other games, fractal mathematics) but, as long as they are well understood, that really doesn’t matter. You may be able to come up with exceptions to the “every program is a simulation” rule,1 but the very fact that you have to think for a while to do so is a significant sign that this claim is plausible,
If you buy the first part of this philosophy, then the second part becomes really interesting. Ever listen to another programmer explain a design and wonder how the heck that was going to solve the problem at hand? Ever notice how programmers love to make up new terms for things in their code, often in defiance of the normal English-language meaning of the word? Or, even worse, ever read code where the variables had names like info or data or value or the functions had names like processData? Does anyone think names like those {\em really} have any informational content?
The OO philosophy suggests that the things manipulated by the program should correspond to things in the real world. They should carry the same names as those things carry in the real world. And they should interact in ways like those objects n the real world.
Design from the World
-
Walk into a library and what do you see?
-
Books, Librarians, Patrons (customers), Shelves, …
-
In an older library you might actually see a card catalog!
-
Of course, on the way in the door you saw the building/Branch Library.
-
-
These things2 form a set of candidate ADTs.
-
Next we would explore the various properties and relationships that these things exhibit in real life:
-
In our [earlier example]{doc:functionalDesign), we considered a function-based approach to design, coming up with subsystems like RecordStorage, CheckInOut, AccountMgmt, and InventoryMgmt.
In the OO approach to our earlier library design, we might never arrive at those. Instead, we would draw upon our knowledge of the world of libraries and say that this world is populated by Books, CardCatalogs, Librarys, LibraryBranches, Patrons, etc., and these would become our initial set of ADTs, our modules for the system design. If our personal knowledge of the world of libraries is not quite up to snuff, we can ask the domain experts, the librarians and staff and patrons of the library.
Real-World Relationships
-
Books have titles, authors, ISBN, …
-
Librarians and Patrons have names. Patrons have library cards with unique identifiers. Most librarians are also patrons.
-
Patrons return books. Librarians check in the returned books.
-
Librarians check books out for patrons. (At some libraries, patrons can also do their own check-out.) Librarians handle the processing of newly acquired books.
-
Although books can be returned to any branch, each book belongs to a specific branch and will, if necessary, be delivered there by library staff.
Organizing the World
OO designers would start by organizing these things into ADTs, e.g.:
(These are examples of classes as diagrammed in UML. We introduced this notation earlier.)
Simulate the World’s Actions
Only then would OO designers consider the specific steps required to add a book to inventory:
“update the electronic card catalog so that the book can be found, and record that the book is present in the inventory of a particular branch”
Simulate the World’s Actions
function Librarian::acquisition (Book book)
{
BranchLibrary branch = this.assignment;
Catalog catalog = mainLibrary.catalog;
catalog.add (book, branch);
branch.addToInventiory (book);
}
function Catalog::add (Book book)
{
authorIndex.add (book);
titleIndex.add (book);
subjectIndex.add (book);
}
OO Approach - Observations
Technically, does the same thing as the earlier design,but
-
Division into ADTs is a natural consequence of the “observational” approach
-
More concern with accurate, domain-appropriate terminology (“acquisition” rather than “addBook”, “patron” rather than “customer”, “stacks” rather “shelves”
-
Avoids programmer-isms such as “BookInfo”
-
-
Separation of concerns is better (e.g., catalog functions are not built into the librarian code)
What’s the advantage to this?
- We can talk to the domain experts (librarians). Because we talk about things in the library world (not in some made-up-by-the-programmer world), their knowledge of how things really work is immediately relevant to our design. Because we use their terminology, we avoid a lot of frustration and wasted time explaining artificial and misleading programmer-ese terms:
“OK, so the LB database…”
“The what?”
“The LB database - that’s where we store the information that used to be in the card catalog. OK, the LB database has to store info about more than just books. You have magazines, CDs, DVDs, etc. So the LB database can contain any kind of shelvable item.”
“Shelvable item? You mean any kind of publication, right?”
“Uh, yeah. We decided to call them shelvable items because they represent anything you can put on a shelf. Anyway, the LB database can…”
- We can talk to each other on the development team. Anyone working on a software project eventually has to gain a certain knowledge of the application domain. That knowledge carries with it a whole host of expectations as to what is and is not relevant, how things behave, who does what, etc. If we know that, in the real world, librarians check out books, then when we are hunting for the code responsible for checking out books, we should feel safe in assuming it will be a function of the Librarian ADT, and that it will be named something like checkOutBook, not some bit of programmer-ese like changeBookLocationToCustomer.
Grady Booch suggested the “principle of least surprise” as a guide to designing ADT interfaces. The idea is that, given that we all share a certain common understanding of how things are supposed to work, any surprises we encounter when reading someone’s design are invariably unpleasant ones, representing a place where the design deviates from our expectation of how it would be most likely to work. Each such surprise is something that we are going to have to remember later, whenever we try to work with that particular designed component. If the choice of ADTs is “natural” to the application domain, if the names are natural, if the interactions (function calls) between them are natural, we have fewer surprises to cope with and fewer diddly little details likely to go wrong.
- We can understand our own work better. Ever go back after a couple of months, read your old code, and wonder “What in the world was I thinking?” That’s just another kind of surprise, delayed. Again, if the structure of the program reflects our natural understanding of the world in which the program resides, we should avoid many of these unpleasant surprises.
Hopefully, then it’s clear how an OO approach can have a big impact on the design of our programs. Closely related to OOD is OOA. If “design” is all about figuring out how to make a program do what we want, “analysis” is about first figuring out what we want it to do. But programs don’t run in isolation. They interact with objects in the surrounding world. So it should not come as a shock that thinking about the world as a collection of interacting objects helps here too. If real-world librarians check out books for library patrons, then isn’t it natural to assume that an automated librarian would check out books for library patrons?
1.2 Simulation == Modeling
OO Analysis & Design
-
Analysis is the process of determining what a system should do
-
Design is the process of figuring out how to do that
Models
We can rephrase that as
-
Analysis is the construction of a model of the world in which the program will run.
-
including how the program will interact with that world
-
-
Design is the construction of a model of a program to work within that world.
Note our approach in the earlier library examples: we built a model of the library world, and took for granted that this was the basis for design of library code.
Steps in Modeling
-
Classification: discovering appropriate classes of objects
-
Refined by documenting
-
interactions between objects
-
relations between classes
-
Both of these steps are often driven by scenarios.
2 Putting the “Programming” in OOP
OOP in Programming Language History
Language Concepts | Examples | Design Concepts |
---|---|---|
Statements | Stepwise Refinement | |
Functions | FORTRAN (1957), COBOL, ALGOL, Basic (1963), C (1969), Pascal (1972) | Top-Down Design |
Encapsulated modules, classes | Modula, Modula 2 (1970), Ada (1983) | Information hiding, ADTs |
Inheritance, subtyping, dynamic binding (OOP) | Smalltalk (1980), C++ (1985), Java (1995) | Object-Oriented A&D |
So, if the point of OOA and OOD is to come up with good ADTs, why do we need OOP? In fact, many OODs lead to programs that could be implemented very nicely in a pre-OO programming language that had good support for ADTs. But certain patterns of behavior crop up again and again in simulations, patterns that could not be easily supported in those earlier languages. The languages that we call object-oriented evolved to support these behaviors. This does not replace support for ADTs - all OOPLs start with good support for ADTs. But then they add to it the capability for class- or object-variant behaviors.
2.1 What is an Object?
Maybe you thought I was never going to get around to this?
An object is characterized by
-
identity
-
state
-
behavior
2.1.1 Identity
Identity is the property of an object that distinguishes it from all others. We commonly denote identity by
-
names, such as Steve, George,
-
in programming, x, y
-
-
references that distinguish without names: this, that,
- in programming, *p
2.1.2 State
The state of an object consists of the properties of the object, and the current values of those properties.
For example, given this list of properties:
struct Book {
string title;
Author author;
ISBN isbn;
int edition;
int year;
Publisher publisher;
};
we might describe the state of a particular book as
Book cs330Text = {
"Object Oriented Design & Patterns", horstmann,
"0-471-74487-5", 2, 2006,
wileyBooks};
2.1.3 Behavior
Behavior is how an object acts and reacts to operations on it, in terms of
-
visible state changes and
-
operations on other visible objects.
OK, identity, state, and behavior are nice ideas. But what’s it really add up to? How do you know if you have a good object?
Personally, I’m rather fond of the “kick it” test. If you can kick it, it’s an object. Returning again to our library example, Books, CardCatalogs, Librarys, LibraryBranches, Patrons are all physical tangible objects. If I came up to one, I could give it a kick. (Some of them might kick back!) So I think I can accept all of these as classes of objects.
What about RecordStorage, CheckInOut, AccountMgmt, and InventoryMgmt. Have you ever walked down a hall and tripped over an InventoryMgmt? Could you kick/touch/hold a CheckInOut? That’s a pretty good clue that these are not objects.
Object (and class) names are invariably noun phrases. A good rule of thumb is that tangible objects in the real world make good objects in the simulated world. This includes people. However, not all simulated objects need to be tangible. Events are often objects. Roles played by people in a system are often objects.
Some things that are terrible choices for objects early in the system development become acceptable later on. For example, is RecordStorage an object?
-
It could be. It does not require a whole lot of imagination to believe that it would have identity, state, and behavior.
-
The name isn’t great – it smacks of programmer-ese.
-
More importantly, there probably isn’t one of these in the library right now, except in the form of the card catalog, in which case we are better off with a cardCatalog object.
But later on, when we are deep in design, if we make a design decision to unite the card catalog and circulation and inventory records into a database, calling that a RecordStorage object might not be quite so awful (though I’d still prefer a more descriptive name). That’s because, although this thing does not exist in the old unautomated world, it will exist in the new world that includes our automated system.
2.2 Messages & Methods
In OOP, behavior is often described in terms of messages and methods.
A message to an object is a request for service.
A method is the internal means by which the object responds to that request.
Differing Behavior
Different objects may respond to the same message via different methods.
Example: suppose I send a message “send flowers to my grandmother” to …
In using objects, we must know what messages they will successfully respond to, not necessarily the method of the response. But how can we even know whether a group of objects respond to some common message? All OOPLs provide some way to organize objects according to the messages they accept. Most common is to group them by class.
2.3 What is a Class?
A class is a named collection of potential objects.
In the languages we will study,
-
all objects are instances of a class, and
-
the method employed by an object in response to a message is determined by its class.
-
All objects of a given class use the same method in response to similar messages.
By now you might start to wonder if objects and classes are really all that new, or if I’m just playing terminology games with you. In fact, all the terms I have introduced in this lesson have direct correspondences to more traditional programming terms:
Are Objects and Classes New?
OOPL | Traditional PL |
---|---|
object | variable, constant |
identity | label, name, address |
state | value |
class | type (almost) |
message | function decl |
method | function body |
passing a message | function call |
So all the new vocabulary we’ve been introduced to has existing equivalents in the traditional programming world.
One advantage to the new vocabulary is that it helps get you in the mindset of thinking of the program as a simulation of actual objects.
Classes versus Types
Although “class” and “type” mean nearly the same thing, they do carry a slightly different idiomatic meaning.
-
In conventional PLs, each value (object) is an instance of exactly one type.
-
In OOPLs, each object (value) may be an instance of several related classes.
Although you can interchange “type” and “class” in many statements, the use of the word “type” carries with it an implication that only one is involved.
How is it possible for one object to be a member of multiple classes? It happens in the real world all the time that we divide things into multiple levels of ever-finer classes: This occurs because classes can be related via “inheritance”.
Instances of Multiple Types
- Classes can be related via “inheritance”.
-
Inheritance captures an “is-a” or “is a specialized form of” relationship.
-
If you have ever played the game “20 Questions”, you may be familiar with the stereotypical opening questions:
“Is it an animal?”
If the answer is no, this is followed with “Is it a vegetable?” (meaning any plant at all). If the answer to that is false, the object is known to be a “mineral” because only tangible objects are considered fair game in most versions of 20 Questions.
If the answer to the animal question were “yes”, however, this is generally followed by questions regarding its diet: “Is it a carnivore?”, “Is it an herbivore?” and so on. An answer of “yes” to the herbivore question might be followed by “Does it eat grass?” (ruminants).
Inheritance Example
The diagram here shows that we are progressively restricting ourselves to more specialized classes. Now, clearly Bessie the cow (a specific object) is a member fo the class Cow but that is not all. She is simultaneously a member of the class Ruminant and of the classes Herbivore, Animal, and of the class of valid 20 Questions objects.
This ability to model some classes ans specializations of others is, together with the ability to implement variant behaviors in response to a common message, precisely what traditional PLs were unable to support but that OOPLs were designed to allow.
How do we know if a group of objects will respond to a message? If they are all members, at some level, of a class that supports that message. (Later we will look at an additional mechanism often employed for this purpose, “subtyping”.)
How does an object determine what method to use in response to a message? It uses the method associated with the most specific (specialized) class to which it belongs. We will later see that this rule is referred to as “dynamic binding”.
What makes a PL an OOPL?
- It must provide support for variant behavior
-
Usually accomplished via dynamic binding
-
- It must provide a way to associate common messages with different classes.
- Usually accomplished via
- inheritance, and
- subtyping
- Usually accomplished via
We’ll learn what these more specialized terms mean as the semester progresses.
1: Designing and writing compilers might be an example. The thing that compilers do is simply not something that anyone does manually in the real world, and, if they did, the techniques used in a compiler are unlikely to be anything like what, say, an Assembler-language programmer would use in writing Assembler code based upon a higher-level pseudo-code design.
2: Emphasis on the word “things”!