As you may already have read in my previous posts, one of my biggest pet peeves in the IT industry today is that a lot of people are using Object Oriented Programming (OOP) without even understanding the basic concepts behind it. They’re not using OOP for its benefits over the previous generation’s simple structured procedural programming; they’re using it simply because the language they’re coding in is OOP (e.g. Java, .NET).
To put it bluntly, it’s like writing spaghetti code in a structured programming language.
With this being my perspective about the local status quo, I feel that I have to post about the basics of OOP before moving on to the other fun stuff related to programming.
Before we proceed, take note that OOP is just one part of the Object Oriented (OO) software construction approach. There’s also Object Oriented Analysis (OOA) and Object Oriented Design (OOD). This post will primarily cover OOP and will only touch OOA and OOD when concepts from OO (which is shared by the three) are discussed.
With that out of the way, let’s begin this short walkthrough of OOP.
Why Object Oriented?
I’ve already mentioned above that “Because we’re programming in Java/.NET” is the wrong answer to this question. What is the correct answer then?
For me, the benefits of OO can be summarized to two main points: simplicityand modularity.
Simplicity – by looking at the problem domain as a bunch of objects each with a different set of behaviors, not only do we help our brains visualize the system, the reduced complexity also translates well into code complexity. In a recent InfoQ presentation, the speaker talks about how he could turn a 200,000 line report generator program in COBOL to just “four classes and maybe a hundred lines” of Smalltalk code.
Understanding that managing complexity is one of the goals of OO exposes one of the pitfalls of OOP programmers: making things more complicated than they should be. If you’re going to write a complicated 10,000 line program in OOP which could have been done in less than a hundred lines of code in a scripting language (say, a shell script), you’re doing things wrong.
Modularity – Objects can be made up of other objects, and they can be derived from other objects. However, objects should be independent from each other as much as possible. When you change the internal details of an object, it should not break the other objects depending on that object. For example, changing the number of pistons in an “
Engine” class may increase or decrease the performance of the “
Car” object using that
Engine, but it should not change the expected behavior of the
Car (e.g. stepping on the accelerator would not make the car move backwards). This lack of interdependence among objects allows multiple developers to work smoothly at the same time on large systems.
Unfortunately, many programmers do not strive to make their objects modular. Not only does this result in unexpected problems popping up from different places when minor changes are made to the system, this approach also increases the complexity of the objects — a violation of the first goal listed above.
All of the other benefits of proper use of OO follow from these two points.
Leanness, explained by the Antoine de Saint Exupéry quote “It would seem that perfection is attained not when no more can be added, but when no more can be removed,” is a direct result of simplicity.
Extensibility and reusability are direct results of modularity.
Stratification (consistent representation per level of the system, e.g. when we’re at the level of
Cars, we just see
Car related stuff, when we’re at the level of
Engines, we just see
Engine related stuff, and so on) and eliminating redundancy are results of combining simplicity with modularity.
Basic OO Concepts
Here we run through the definitions of the basic OO concepts.
Repeating what I said above, OO is basically just modeling the problem domain as a bunch of objects each with a different set of behaviors. This means that an object is a representation of an item within the system (think “noun”: person, place, thing, quality, process, etc.), each with a set of properties (e.g. a
Light Bulb is currently on, a
Car has 4 wheels) and behaviors (e.g. a
Light Bulb can be turned off or on, a
Car can move forward).
Note that objects only exist when the system is running. When we are still designing and coding the system, we are mainly dealing with classes. The difference between the two is that the former is an actual instance of the concept, while the latter is merely the design specifications. To give an example, take the case of a
House. The blueprints of the house would be the class, while house itself is the object.
Objects interact with other objects in the system via message passing. In some languages (e.g. Java), this is simply done by calling methods. Other programming languages have slightly different implementations of message passing.
After these basic concepts, we move on to the four concepts that form the foundation of OO:
Abstraction – what we’ve been doing all along, modeling real-world objects into classes. There are some things to note about this concept, though. First, we must make sure that the level of abstraction is appropriate for the class: we should not have too much or too little properties and behaviors in the class, and we should also apply the necessary level of inheritance, composition, and encapsulation (which will be discussed below) to it.
Another thing to note is that unless the abstraction is trivial, you cannot fully model a concept without having to “break” the concept whether by leaky abstractions or some other limitation of OO that prevents us from making perfect abstractions. With that in mind, it is important one must only strive to achieve an appropriate level of abstraction as mentioned in the previous note.
Encapsulation (or Information Hiding) – as the second name implies, encapsulation is all about hiding the parts of your objects that other objects don’t need to access or modify. Without encapsulation, you will have the same problem as using global variables in procedural programming, that is, you are opening parts of your classes to misuse. Another object might directly modify some of the states of your object without taking into account the pre-processing or the side-effects of the change. Another object might call a method in the object that should only be called by the object itself.
Ideally, a class should hide all of its properties and should only expose the behaviors that are directly needed by other classes.
Inheritance – in OO, you can extend a class in order to reuse all of its properties and behaviors. For example, you’ve already designed a
Person class and you need to design a
Student class. A
Student is also a
Person so you need to define the same set of properties and behaviors in the former. Without inheritance, you would have to copy-paste everything from
Student. This redundancy is dangerous: if you have to modify the
Person class, you’ll also have to look for all classes like
Student which copied from
With inheritance, however, you can simply declare
Student as a subclass of
Person. This will automatically make
Student inherit all of the properties and behaviors from
Person, and you can now focus on adding
Student-specific properties and behaviors.
Polymorphism – you can allow subclasses to “stand in” for their parent classes. The “polymorphism” part here comes from the ability to change (or override) inherited behaviors so that they behave differently per subclass.
Polymorphism makes sense when we introduce the concept of interfaces, classes that defer their behavior to their subclasses. For example, we can have a
Shape interface that has a blank “calculate area” behavior. It is now up to the subclasses, e.g.
Triangle, to define how “calculate area” works for their case.
You might be wondering what is the point of all of this. It may not be apparent, but this approach helps in making the abstraction more consistent because this leads you to care about the interface rather than the implementation. Following our
Shape example, if you’re given a
Shape object, that object may actually be a
Square or a
Circle (as polymorphism allows a subclass to stand in). You can calculate the shape’s area even without knowing its actual features by calling the “calculate area” behavior of the
Shape then leave the rest to the computer.
Without polymorphism, you are forced to break the abstraction of the
Shape. It will no longer be just an ambiguous shape with some defined behaviors, but a shape with various unrelated properties like “radius” (for
Circle) and “side length” (for
Square and other regular polygons). Not only have we broken abstraction, we are also at risk of breaking encapsulation.
It’s ok if you don’t understand the paragraphs above. I’ll be providing further examples on polymorphism on a succeeding post.
Aside from these four concepts, here are a few more OO concepts that we might discuss later on:
Composition is building objects from other objects. For example, a
Person is made up of body parts like
Lungs, etc. One of the first things an OO practitioner must learn is to know when to use inheritance and when to use composition. Inheritance is an “is-a” relationship e.g. a
Person but a
Person is not an
Ear. Composition is a “has-a” relationship e.g. a
Ear but a
Student doesn’t have a
Person (well.. technically).
Don’t laugh if you think that it’s impossible for someone to mix up these two simple concepts. Using inheritance for composition and vice-versa in the name of reuse happens quite often in software systems. A facepalm would be a better reaction.
Class variables and class methods are properties and behaviors that don’t require an instance of the class in order to work. On the other hand instance variables and instance methods only work on an instance of that class. For example, given a
House class, respective examples can be:
|class variable||number of floors in the house according to the blueprints|
|class method||calculate total floor area according to the blueprints|
|instance variable||number of people currently inside the house|
|instance method||lock the house|
Coupling is the level of interdependence between objects. As you may have guessed, good systems have low coupling between their objects (i.e. loosely coupled).
Cohesion defines the amount of focus an object has to its responsibilities. High cohesion is preferred as this results in simple and easy to maintain code. This concept is also related to coupling: weakly cohesive objects perform the responsibilities of other objects, a sign of tight coupling.
And that’s about it for this post. Next post will cover the basic principles related to OOP.