Object Oriented Programming/Introduction To OOP
Introduction
[edit | edit source]For an overview and history of Object Oriented programming OOP, please reference the Wikipedia article.
The reader is expected to have a basic familiarity with programming in general, as we will give examples in a variety of languages. We will explain any non-obvious syntax in the discussion, although this is beside the point. The point is to give some indication of the flavor of the languages and some insight into the real-world application of OO ideas.
Overview
[edit | edit source]We will divide up OOP into two phases— classic and modern. While this distinction is somewhat arbitrary, we believe it is instructive to consider OOP as it was practiced in the 1980s and early 1990s to demonstrate the motivation for more current practices.
What is Classic OOP?
Object oriented programming can be traced back to a language called Simula, and in particular Simula 67, which was popular during the 1960s. It was Simula that first instituted "classes" and "objects," leading to the term "object oriented" programming. By the early 1990s, enough experience had been gained with large OOP projects to discover some limitations. Languages such as Self, ideas like interface programming (also known as component or component-oriented programming), and methodologies such as generic programming were being developed in response to these difficulties. Although often derided by OOP purists, it was the standardization of C++ in 1998 —including generic programming facilities — that really ushered in the modern era of OOP, which we also refer to as Multi-Paradigm programming. This is largely due to the popularity of C++ and the genius of the Standard Template Library (STL) demonstrating the utility of the new methodology to such a large audience.
Classic or Pure OOP
[edit | edit source]By 1980, Xerox had made Smalltalk available to outsiders, appropriately named Smalltalk-80. Unlike other early programming languages, Smalltalk was a complete environment rather than just a language, a characteristic it had in common with Lisp at the time. While Lisp machines were foreshadowing IDEs to come, Smalltalk was pioneering the GUI, ultimately influencing the development of the Macintosh computer. Meanwhile, while Xerox was developing Smalltalk during the 70s, the C language was becoming popular thanks to UNIX being largely written in C. Therefore, it was C —an otherwise unlikely candidate —that Bjarne Stroustrup fused with ideas from Simula to create "C with Classes" which was renamed to C++ in 1983. In 1985, unsatisfied with Smalltalk, C++, and various object systems being tacked onto Lisp dialects, Bertrand Meyer created Eiffel. Even though much more recent, Java essentially cloned vintage C++, and so we consider Smalltalk, vintage C++, Eiffel, and Java to be Classic OOP. The object systems tacked onto Lisp (eventually standardized as CLOS in 1994) produced a very different methodology. While we don't consider CLOS to be Classic OOP, it did influence modern OOP.
Modern OOP and Multi Paradigm Programming
[edit | edit source]Classic OOP developed a tendency to over-rely on a technique called "inheritance," and eventually programmers realized that they were using inheritance for many things that are conceptually distinct. Modern OOP basically incorporates these concepts, sometimes as language level features, sometimes through programmer practice. The goal largely being looser coupling, easier maintenance and reuse. Historically, David Ungar and Randall Smith completed their first working Self compiler in 1987, and by 1990 Sun Microsystems had picked up the project. While Self did not survive to really become a modern OOP language, it was a second generation OOP language. Then, in the early 1990s, Alexander Stepanov and Meng Lee pioneered generic programming and wrote early drafts of C++'s STL. This began a (still ongoing) trend of incorporating functional programming ideas into more traditional OOP environments, in a kind of reverse CLOS. Additionally, the early 1990s saw the development of CORBA and Microsoft's COM, which was a natural extension of the ideas that had led to the original Windows API. This interface or component programming was a natural extension of encapsulation —a basic tenet of OOP, as we will see. All of these developments were aimed at further managing or reducing complexity. Since this was the original goal of OOP and these techniques are used in conjunction with classic OOP, we find it appropriate to consider them in a treatment on "OOP".
Contemporary object oriented programming, therefore, tends to be rather distinct from classic object oriented programming. Particularly, getting used to which abstractions are the most useful for which problem types is more challenging now that there are more from which to choose! In a now classic book, Gamma et. al. introduced Design Patterns which helped to synthesize a variety of OOP techniques as applied to very common problems.
Future of OOP
[edit | edit source]The future contains more standardization of functional programming techniques in OOP environments, particularly lambda expressions and closures, and more robust meta-programming constructs. Applying design patterns automatically through generic or meta programming techniques is an interesting area.
What is an Object?
[edit | edit source]Loosely, the term object is used to conjure up connections with real world objects like a chair or a guitar. Except that for software, only some simplified abstraction is used, designed specifically for the task at hand. While a real chair is composed of atoms and molecules and reacts to its environment based on the laws of physics and its atomic composition, a "chair object" will vary wildly depending on whether you're writing a game or a Point-of-Sale system for a furniture store. Approaching your problem from the perspective of the chair will not be as productive as approaching the chair from the perspective of your problem.
In most of the languages used in this book, you will find that technically speaking (something akin to whistling 9600 baud, I understand), an object is "an instance of a class". Great, so what does that mean? Well, we can trace this idea all the way back to Plato and his Platonic Ideal. If you spent more time watching Bill and Ted's Excellent Adventure than reading Plato, the idea is that the concept of a chair is a separate entity from any particular chair. In other words, you can conceive of a chair in the abstract without thinking of any particular chair.
In most OOP languages, this abstracted idea of a chair is called a class (from classification) and is a prototype or blueprint for actually making chairs. The act of making something from the blueprint is often called instantiating, and the made thing is both an object and an instance of the class that served as a blueprint. As humans, we normally tend to do this in reverse —we categorize objects we encounter. We can easily identify chair-like things we run into as being chairs; this classification is where we got the term class in the first place.
It is easy to drift off into abstruse philosophical debates over objectness; in some areas like knowledge representation and computational ontology they are very relevant. However, for a computer programmer, it is only necessary to figure out what your application needs to know about and do with chairs. This can be a sufficiently difficult problem, it's usually not necessary to make it harder!
If you're still fuzzy on the whole idea, consider a more technical explanation. Structs (structures), records, tables, and other ways of organizing related information predated object oriented programming. You may be familiar with something like the following Pascal code:
TYPE chair = RECORD
model : integer;
weight : integer;
height : integer;
color : COLOR;
END;
This doesn't actually create a chair variable, but defines what a chair variable will look like when you create one. You could proceed to create arrays of chairs and so forth, and as we hope you've discovered for yourself, this kind of thing is quite indispensable for keeping your programs understandable. Object oriented programming wants to push this advantage and milk it for every ounce of understandability, correctness, and simplicity it can.
A fundamental problem solving technique is divide and conquer —you just have to figure out how to partition your problem into sub-problems. OOP innovators realized that we already had figured out ways to partition the problem, and it was reflected in the way we organized our data, like above. If you looked at an application that had that chair RECORD in it, you would surely find lots of code for doing things with chairs. Why else would you bother to define it? So, if you were to extract all of that code from all over the application and put it together with the chair definition, the reasoning goes, you should have an easier time ensuring that:
- all chair code is correct
- all chair code is consistent with each other
- there's no duplicated chair code
- overall, you have less spaghetti because chair code is no longer tangled up with sofa code etc
So you take that chair definition and that code extracted from all over the application, and call that a class. Take a chair variable and call it an object, and you're off to the races.
Since this is supposed to be a pragmatic book, let's look at our first example code, this time in Python code:
class Chair:
model = None
height = None
weight = None
color = None
def has_arms(self):
return self.model % 2 # odd-numbered models have armrests
This doesn't look terribly different from the Pascal example. The only difference is that class Chair now contains a has_arms method. Methods are functions that live within a class, and are usually used to process certain class specific data. Hopefully the purpose is fairly clear, so let me point out the important part: has_arms is a calculated or inferred property—this information is not stored directly.
In Python, you would use this class like so:
c = Chair()
c.model = 15
c.height = 40
c.weight = 10
c.color = 7
if c.has_arms():
do_something
else:
do_other_thing
Here, the "c" variable is an instance of the 'chair' Class, also known as a chair object. We initialize the properties like you would in Pascal (this will change later!) and do one thing if the chair has arms and something else if it doesn't. But we never initialized a "has_arms" property or anything of the like.
Since part of the goal is for you to become syntax agnostic, we present this same example again in C++:
class Chair
{
public:
int model;
int weight;
int height;
int color;
bool has_arms()
{
return model % 2 ? true : false;
}
};
Chair c;
c.model = 15;
c.height = 40;
c.weight = 10;
c.color = 7;
if (c.has_arms())
do_something();
else
do_other_thing();
Now, we would just like to mention that this is just the tip of the iceberg and this example is not representative of good style (in either language). There may seem like little purpose to making objects with so little functionality, and you could easily generate equivalent code that requires no new class:
struct Chair
{
int model;
int weight;
int height;
int color;
} c;
Chair c = {15, 40, 10, 7 };
if (c.model % 2)
do_something();
else
do_other_thing();
The purpose of this section is to help you understand the terms; we will delve more into the benefits in the sections on encapsulation, polymorphism, inheritance, and on and on. However, let us say that while the "low-level" way might seem shorter and simpler, the object oriented benefits accrue with program size and complexity. This is not surprising, as that's what OOP was designed for, but it does make simple examples hard to come by. So bear with us.