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 Car
s, we just see Car
related stuff, when we’re at the level of Engine
s, 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 Person
to 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 Person
.
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. Circle
, Square
, 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 Hand
s, Ear
s, Lung
s, 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 Student
is-a Person
but a Person
is not an Ear
. Composition is a “has-a” relationship e.g. a Person
has-an 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.
I never really got the difference between OOA, OOD and OOP. I thought that they were just different terms for the same thing.
I never really got the difference between OOA, OOD and OOP. I thought that they were just different terms for the same thing.
They use the same concepts. The only difference is that they’re applied to different phases in software development. For example, you probably won’t be able to do a lot of refactoring at the micro level in OOA.
They use the same concepts. The only difference is that they’re applied to different phases in software development. For example, you probably won’t be able to do a lot of refactoring at the micro level in OOA.
Ohhh… The OO approach just seems to work in a lot of areas of Software Dev.
Ohhh… The OO approach just seems to work in a lot of areas of Software Dev.