Object-Oriented Thinking, Part One

2009-12-29

For me, starting to learn Object-Oriented
Programming (OOP) was quite the pain. It wasn't what I'd
grown accustomed to over the past 15 years, and I was learning C++ at the same time
from a prof who was also learning these things at the same time. (Not
that there's anything wrong with that—profs do it all the time—but
sometimes it's not the best learning environment.)

One of the consequences of this was that we were all caught up in
the minutiae of C++, and kinda skimped on the big OOP picture.

So here it is: "OOP is good if you have a bunch of things that have
attributes and do stuff." OOP wraps up the things, their attributes, and
the stuff they do in a nice way that can make the code clean and clear.
Things like GUIs (which
have a bunch of things like windows and buttons, and the windows and
buttons have attributes like their locations on the screen, and they can
do stuff like get clicked or move) are natural fits to OOP.

Three Antelope instances. The one on the left
is calling its move() method, and the one in the middle is
calling its eat() method.(Photo by Mr Raja Purohit)

In OOP, you have particular "classes" of things, for example
"Baxter", "Jane", and "Sue" could all be antelopes (of class
"Antelope"). In this case, they are all called "instances" of the
"Antelope class", or "they are instances of Antelopes".

So the class definition, in a way, is the master description of what
data the instances will have and what stuff the instances will be able
to do. In general, the class doesn't really do anything on its own—you
make new instances based off it. (The preceding sentence, like most
everything in this post, has exceptions, but you can worry about those
later.)

Do antelopes have attributes? Yup! In this example, they have a
"stomach fullness" and a "location" (described as an x and
y coordinate). The class would describe how these attributes were
stored.

Do antelopes do stuff? You bet! They can "eat a vegetable" and "walk
a direction". The class would describe what these actions did. These
actions are often called methods in OOP parlance, and consist of
code that does stuff—other programming paradigms keep their code in
"procedures", "subroutines", or "functions", but OOP has "methods".

In many OOP languages, the code for defining the class is in a
difference place than the code that makes the instances. For the
moment, let's take the code for the Antelope class definition, here
written in Java:

Notice that the code on its own isn't actually doing anything; it
merely describes what attributes an Antelope has and what its actions do
(its "methods".)

Before we can actually do anything with Antelopes, we need to make
instances of them (doing this is known as "instantiating Antelopes".)
You can't do anything without any Antelope instances. The good news is
you can make as many instances of Antelopes as you want! Feel the
power!

Another thing to see in the above code, if you're not familiar with
OOP, is the "this" keyword. It's a little bit funky to think
about, but this refers to "this instance of the Antelope
class"—it only makes sense in the context of an instance. So if we have
three instances of Antelope, baxter, jane, and sue, when jane refers to
"this.xLocation", jane is referring to her
"xLocation" value. baxter and sue have their own
"xLocation"s. ("This is my 'xLocation'. There are many others
like it, but this one is mine.")

Some languages, like Python and Objective-C, call "this
instance of the class" "self" instead of "this".
Further complicating matters, in some languages like Objective-C, C++,
and Java, this is optional—the compiler will automatically
prepend it if it can't find a variable of the same name in scope.
Personally I always explicitly write this so other engineers
are clear that I'm referring to the object's instance variables.
(Objective-C takes this even farther and writing "self.foo" and
"foo" can have different effects even if they both refer to the
same instance variable! But this is way more than I want to talk about
here.)

Now, you probably notice that I snuck some code in the definition of
the Antelope class that I hadn't previously mentioned: the
Constructor. The constructor is the code that executes on a new
instance of a class when that instance is first brought to life
("constructed", or "instantiated".) It's very common to put
initialization code in there. Also in this case, I allow the
constructor to be passed a parameter: the Antelope's name. This name is
used later in the eat() and move() methods so that
when the program runs, the user will be able to tell which Antelope
instance is doing what. The constructor dutifully stores a copy of the
name reference in the instance so it can use it later.

In Java, the constructor looks like a method with the same
name as the class. Other languages might be similar (like C++) or
completely different (like Python, where the constructor is always named
__init__().)

In any case, it's time to fabricate some Antelopes! We have now
defined the "Antelope" class, and it's time to make some instances and
use them in the main program. Then we'll have them move and eat!

At the top of main(), you see we construct three Antelopes
right away with the new keyword. (new tells Java to
instantiate a new instance of a class.) This causes the Antelopes to be
allocated and their constructors called—you see we pass the constructor
a human-readable version of their names, nicely capitalized.

Following this, baxter is told to move north, jane
is told to move west, and sue is told to eat a veggie of size
10. When these methods are called, each instance will print the result
(as you see in the Antelope's move() and eat() code.)
More jargon: "calling an object's method" is sometimes referred to as
"passing a message to an object" in OOP contexts.

Finally, the main routine prints out the value of jane's
name instance variable.

Here's the output:

Baxter has moved to (0,-1)
Jane has moved to (-1,0)
Sue now has fullness 10
jane's name is: Jane

Now, you remember those "private" and "public"
keywords I told you about? To revisit those, they tell you which
variables and methods are visible outside the class. In the case of the
Antelope class, anyone can access the name instance variable
because it's public. (Indeed, the main code does this when it prints out
baxter's name.) However, stomachFullness is private,
and can't be accessed from anywhere except inside the Antelope class.
(If main were to try to print it like it prints the name, it would be a
compile-time error.)

The reason for making class members private is to hide the
implementation details of the Antelope from the outside world in order
to increase the ease-of-use of the Antelope, as well as increasing the
reliability and maintainability of the code. As long as no other code
in the entire Universe refers to the private instance variable
stomachFullness it can be changed in (or even removed from) the
Antelope class later without a problem—declaring it to be private
ensures no one else can use it. Likewise, declaring something to be
"public" allows people to use it, and you won't be able to change it
later without also overhauling all the code that uses it. This is why
it's very important to be sure about the solid, clean, good
design of your public class elements before sharing them with the
world.

Finally, here's a quick set of guidelines for coming up with your OO
design for a particular problem. First, just describe the problem in
plain English (or the language of your choice). Describe what the types
of entities in the problem are, what attributes they possess, what
actions they can take, and what those actions need to work. ("Well,
we'll have some antelopes and they'll have a location, and they'll be
able to move and eat food, so I guess they'll need a stomach fullness,
then. And when they move, they'll need a direction to go, and when they
eat they'll need to be told the amount.") Then a class can be written
for each type of entity, where the attributes are instance variables in
the class, and the actions are its methods, and what the actions need to
work are passed as parameters to those methods. Lastly, you instantiate
instances of the objects you need, and call their methods!

That's everything there is to know about Object-Oriented Programming!
Of course, that's a lie—this is only the first step out of about a
zillion steps, but it's a very important big first step that's almost as
big as all the other steps combined.

Share me!

Historic Comments

Although I could see the mechanics of OOP, I didn't truly understand the point until I was working a fairly large project, in my case, a MUD.

The code had gotten to the point that is was hard to make even a small change anywhere, without having to go through hundreds of lines of code to reflect that change. If a counter int needed to be made a long because an int was no longer able to fit the data, hours of putting cast operators ensued. By contrast, if the count was contained within a class with a method like Add(), less code would need to be changed elsewhere to remain compatible with that change.
Second, I noticed hundreds of clauses for dealing with PCs (human player characters) and NPCs (computer controlled characters). They both did the same things: They move, they fight, and they have many of the same stats. Because they are different objects, a set of code has to be written for both objects for every situation. The solution: inheritance and polymorphism. A base class which stands for both PCs and NPCs can handle operations which are common to both, while the derived classes can handle situations in which are specific to each.
Well, I apologize for the long rant. The hardest part of learning OOP IMO is not the mechanics, but understanding *why* it is useful.

@Xerion It's so very true that games are excellent applications for OOP. You have a lot of things that have attributes and do stuff, e.g. missiles, NPCs, trains, etc., etc.

And definitely when I get around to writing another OOP article about inheritance I'll bring that kind of thing up. I admit my short post here doesn't give enough of the big picture to know why you'd want to use it, just like you pointed out.

As always, though: the right tool for the job! OOP isn't a cure-all, but it definitely has its place.