Question

Note: this question is an edited excerpt from a blog posting I wrote a few months ago. After placing a link to the blog in a comment on Programmers.SE someone requested that I post a question here so that they could answer it. This posting is my most popular, as people seem to type "I don't get object-oriented programming" into Google a lot. Feel free to answer here, or in a comment at Wordpress.

What is object-oriented programming? No one has given me a satisfactory answer. I feel like you will not get a good definition from someone who goes around saying “object” and “object-oriented” with his nose in the air. Nor will you get a good definition from someone who has done nothing but object-oriented programming. No one who understands both procedural and object-oriented programming has ever given me a consistent idea of what an object-oriented program actually does.

Can someone please give me their ideas of the advantages of object-oriented programming?

Was it helpful?

Solution

Think of software as a machine or assembly line that exists inside the computer. Some raw materials and components are fed into the machine, and it follows a set of procedures to process them into some final product. The procedures are set up to perform a specific operation on some raw material or component to a specific set of parameters (eg, time, temperature, distance, etc) in a particular order. If the details of the operation to be performed were incorrect, or the machine's sensors aren't correctly calibrated, or if some raw material or component wasn't within expected quality standards, it could change the operation's outcome and the product would not turn out as expected.

Such a machine is very rigid in its operation and acceptable inputs. Machines don't question the designers' intelligence nor its current operating environment. It will continue to follow procedures as long as it's directed to. Even if a change in raw materials or components could have a dramatic effect on what happened in later operations, the machine would still perform its procedures. The process would need to be reviewed to see what changes to the procedures were necessary to compensate and produce the desired result. A change to the product's design or configuration might also require a significant change to the operations performed or their order. Although those in charge of production quickly learned the importance of isolating operations as much as possible to reduce undesirable effects between them, a lot of assumptions are made of the condition components are in as they undergo processing; assumptions that might not be detected until the final product is in the hands of the user in some different operating environment.

That's what procedural programming is like.

What object-orientation provides is a way to remove the assumptions of the condition of components; thus, the operations to be performed on that component and how to integrate it into the final product. In other words, OOP is like taking the process details for dealing with some particular component and giving it to a smaller machine to do. The larger machine responsible for the process tells the component-specific machine which operation it expects to be done but leaves the details for the steps to the component-specific machine to handle.

As to the advantages of object-orientation over non-object-oriented software:

  • component-specific behavior - making details on how to handle a particular component the responsibility of the smaller component-specific machine ensures any time that component is handled, its machine will do so appropriately;
  • polymorphic expressions - because component-specific machines performs operations tailored to its particular component, the same message sent to different machines can act differently;
  • type abstraction - it often makes sense for several different types of components to use the same vocabulary for the operations their machines do;
  • separation of concerns - leaving component-specific details to their machines means the process machine only needs to handle the more general, larger concerns of its process and the data required to manage it; plus, it's less likely to be affected by changes in other components;
  • adaptability - components that focus on their area of speciality can be adapted to unforeseen use simply by changing the components it uses, or making it available to another process machine;
  • code reuse - components with a narrow focus and greater adaptability can leverage their development cost by being put to use more often.

OTHER TIPS

From your blog, it seems that you're familiar with both imperative and functional programming, and that you're familiar with the basic concepts involved in object-oriented programming, but you've just never really had it "click" as to what makes it useful. I'll try to explain in terms of that knowledge, and hope that it's helpful to you.

At its core, OOP is a way to use the imperative paradigm to better manage high degrees of complexity by creating "smart" data structures that model the problem domain. In a (standard procedural non-object-oriented) program, you've got two basic things: variables, and code that knows what to do with them. The code takes input from the user and various other sources, stores it in variables, operates on it, and produces output data which goes to the user or various other locations.

Object-oriented programming is a way to simplify your program by taking that basic pattern and repeating it on a smaller scale. Just like a program is a large collection of data with code that knows what to do with it, each object is a small piece of data bound to code that knows what to do with it.

By breaking down the problem domain into smaller pieces and making sure as much data as possible is bound directly to code that knows what to do with it, you make it a lot easier to reason about the process as a whole and also about the sub-issues that make up the process.

By grouping data into object classes, you can centralize code related to that data, making relevant code easier both to find and to debug. And by encapsulating the data behind access specifiers and only accessing it through methods, (or properties, if your language supports them,) you greatly reduce the potential for data corruption or the violation of invariants.

And by using inheritance and polymorphism, you can reuse preexisting classes, customizing them to fit your specific needs, without having to either modify the originals or rewrite everything from the ground up. (Which is a thing you should never do, if you can avoid it.) Just be careful you understand your base object, or you could end up with killer kangaroos.

To me, these are the fundamental principles of object-oriented programming: complexity management, code centralization and improved problem-domain modeling through the creation of object classes, inheritance and polymorphism, and increased safety without sacrificing power or control through the use of encapsulation and properties. I hope this helps you understand why so many programmers find it useful.

EDIT: In response to Joel's question in the comments,

Can you explain what an "object-oriented program" contains (other than these fancy defintions you've outlined) that is fundamentally different from an imperative program? How do you "get the ball rolling?"

A little disclaimer here. My model of "an object-oriented program" is basically the Delphi model, which is very similar to the C#/.NET model since they were created by former Delphi team members. What I'm saying here may not apply, or not apply as much, in other OO languages.

An object-oriented program is one in which all the logic is structured around objects. Of course this has to be bootstrapped somewhere. Your typical Delphi program contains initialization code that creates a singleton object called Application. At the start of the program, it calls Application.Initialize, then a call to Application.CreateForm for every form you want to load into memory from the beginning, and then Application.Run, which displays the main form on screen and starts up the input/event loop that forms the core of any interactive computer programs.

Application and your forms poll for incoming events from the OS and translate them into method calls on your object. One thing that's very common is the use of event handlers, or "delegates" in .NET-speak. An object has a method that says, "do X and Y, but also check to see if this particular event handler is assigned, and call it if it is." An event handler is a method pointer--a very simple closure that contains a reference to the method and a reference to the object instance--that's used to extend the behavior of objects. For example, if I have a button object on my form, I customize its behavior by attaching an OnClick event handler, which causes some other object to execute a method when the button is clicked.

So in an object-oriented program, most of the work gets done by defining objects with certain responsibilities and linking them together, either through method pointers or by one object directly calling a method defined in another object's public interface. (And now we're back to encapsulation.) This is an idea that I had no concept of back before I took OOP classes in college.

I think OOP is basically just a name given to something you may have been tempted to do along the way, as I was.

Way back when I was a baby programmer, even in Fortran, there was such a thing as a pointer to a subroutine. It is really useful to be able to pass a pointer to a subroutine as an argument to another subroutine.

Then the next thing that would be really useful would be to store a pointer to a subroutine inside a record of a data structure. That way, you might say the record "knows" how to do operations on itself.

I'm not sure if they ever built that into Fortran, but it's easy to do in C and its descendents.

So underneath, it's a simple and useful idea that you might have been tempted to do yourself, and is easier to do in more recent languages, even if some people turned it into a giant bandwagon full of scary buzzwords.

There's various sorts of OO systems, and it's hard to get a definition everybody will agree on. Rather than try to show how Java's OO is similar to the Common Lisp Object System, I'll start with something more conventional, step by step.

Suppose you have a lot of objects existing as scattered data. Points, for example, might be elements in an X, a Y, and a Z array. In order to consider a point itself, it makes sense to pull all the data together into something like a C struct.

Now, for any data object, we've got the data all together. However, in a procedural program, the code is scattered. Suppose we're dealing with geometric shapes. There's a large function to draw shapes, and it needs to know about all the shapes. There's a large function to find area, and another for perimeter. The code for a circle is scattered through multiple functions, and in order to add another type of shape we need to know which functions to change. In an object-oriented system, we gather the functions into the same kind of thing (class) as the data. Therefore, if we want to look at all the circle code, it's there in the Circle definition, and if we want to add a Quartercircle we simply write its class and we've got the code.

One side benefit from this is that we can maintain class invariants, things that are true about each member of the class. By restricting code outside the class from directly messing with class data members, we've got all the code that can change class data in one place, and we can confirm that it doesn't do anything screwy (like have a triangle with one leg longer than the other two combined). This means we can count on some properties of every member of the class, and don't have to check to see if an object is sane every time we use it.

The main benefit comes with inheritance and polymorphism. By defining all of these various shapes as subclasses of a class called Shape, we can have our code manipulate Shapes, and it's the job of the shape subobjects to do whatever is called for by the manipulations. This means we needn't touch the old tested code when we add new shapes or refine the behavior of older ones. We automatically have old code that can directly take advantage of new code. Instead of making the controlling code aware of all the different possible shapes, and having to maintain functions that are aware of all the different possible shapes, we merely deal with shapes and their properties, while maintaining Shape subclasses. This simplifies the controlling code.

We have several advantages here. Since we have class invariants, we can reason about larger data objects in the same way we reason about built-in types, meaning we can often split up complex concepts into simpler ones. Since the circle code is largely contained in Circle, we have increased locality. Since there aren't concepts of a circle scattered through several different functions in different places, we get less coupling between routines, and don't have to worry about keeping them in sync. Since classes are, in effect, types, we can take advantage of the existing type system to catch incompatible use of our classes.

OO has many different definitions, yes. I'm sure you can find lots of these on your own. I personally like Rees Re: OO as a way to make sense of them. I'm guessing you've read that already since you quote Paul Graham. (I recommend it to anyone interested in OO.) I'm going to more or less adopt the Java definition here {1,2,3,7,8,9}.

The question of the utility of OO, especially the way I approach it, deserves a much larger answer with a few thousand lines of code (partly in order not to just be a bunch of assertions). However, here's a summary of that hypothetical document.

I don't think OO is terribly useful at a small scale, say, about a few hundred lines. In particular, OO languages without good functional influences tend to make it really painful to get simple things done with any kind of collection or anything that needs many data types. This is where most design patterns come into play; they're band-aids on the low power of the underlying language.

At about a thousand lines, it starts to be harder to keep track of all the operations and data structures and how they relate. It helps, at this point, to have a way to explicitly organize data structures and operations, to draw module boundaries and define responsibilities, and to have a convenient way to understand those definitions while you're trying to program against them.

Java-ish OO is a halfway solution to these problems that happens to have won the popularity contest. Because it's the same mechanism that Java people apply to the small scale problems created by an underpowered language, it tends to start looking more like a magic solution to everything than just a way to stay organized. People familiar with functional programming tend to prefer other solutions, like CLOS or Haskell's type classes, or template metaprogramming when stuck in C++, or else (like me, working daily in C#) use OO but just don't get all that excited about it.

OOP attempts to model real world concepts in terms of objects and interactions between them. As humans, we tend to process the world in terms of objects. The world is full of objects that have certain properties and can do stuff like interact with other objects. OOP allows to model the world in similar terms. For example,

  • Person is an Object. A person has some properties, like age and gender. A person can do things: eat, sleep, drive a car.
  • Car is also an Object (although of different type). It also has properties like make, model and year. A car can do things: move.

But a car can't move on its own, it needs a person to drive it - interaction between Objects.

OOP = data structures + message passing + inheritance, all of which are logical evolutions in programming models.

OOP can be understood (by programmers) in about 90 seconds (see my profile for a link). The concepts are very simple.

How to apply it is another matter. Just because you know how to swing a hammer doesn't mean you know how to design and build a house. ;-)

I wrote a blog post a while ago that you might find helpful: Procedural vs. OOP Explained.

The way I first understood it is :

Before Object-oriented programming, you had Structured programming. Everything is centered around the process. The first question you got to ask yourself is "What I want to do with the information?".

With Object-oriented programming, it's centered around the data. The first question you got to ask yourself is "Witch information I need to deals with?". This make abstraction easier.

Since you understand structs, and you understand function pointers, and you understand structs with function pointers, from your perspective I would define object oriented programming as simply "programming, with heavy use of structs that have function pointers". It's still programming in the traditional sense -- it's all data, and code that acts upon the data. The difference is simply how all of that information is defined and how you approach defining it.

Perhaps an over simplification is that traditional programming is "code, with some data structures", and object oriented programming is "data structures, with some code". Both still have data structures, and both still have code. Object oriented programming, then, is nothing more than the act of defining types of data up front, and enforcing contracts for how they communicate via sets of functions.

As you have observed, there is a huge class of applications for which this is not such a great way to implement a solution. You seem to live in a world predominately made up of such applications. In your blog post you mention looking at implementations of the "99 bottles of beer" problem (your "favorite programming showcase"). 99 bottles of beer is certainly part of that category. Trying to understand object oriented programming by looking at implementations of 99 bottles of beer is a bit like trying to understand high rise architecture by looking at a treehouse. Even a very well built tree house can only teach you so much.

TL;DR: OO programming is like traditional programming, except you focus more of your effort in defining the data structures up front, and you have those data structures communicate with each other via function pointers.

I think the Wikipedia page is a good place to get the fundamentals:
http://en.wikipedia.org/wiki/Object-oriented_programming

Basically the idea is that procedural programming, which is what OOP was trying to improve upon, focused on the processes being modeled. OOP shifts to a model where the focus is on the "things" you are modeling, and the processes and data of those things are contained within those things.

So, as an example, let's say you were designing an application to track a task list. In procedural programming, your top level entities in the model would be the processes that occur, such as creating a task, removing a task, changing the task info, etc. In an OOP model, you would instead focus on creating a Task, and think about what data and processes that Task should be responsible for. And then focus on what other objects Task should interact with, such as possibly a Note or something if you want to keep notes about Tasks.

I hope that helps. Just keep on reading about it and looking at code and it will suddenly "click". That was my experience.

Licensed under: CC-BY-SA with attribution
scroll top