I have been writing object-oriented code (in C++, Python, Ruby) for almost 3 years. But I was only recently acquainted with object-oriented programming.
After writing Ruby code for several months in a huge codebase, I started feeling the pain brought on by my procedural programming habits. Then I read several books about clean code and object-oriented programming (Clean Code, Refactoring: Improving the Design of Existing Code, Practical Object-Oriented Design in Ruby, and Objects on Rails, etc.), and I started to see the light at the end of the tunnel. They really helped me understand what good design is and how to achieve it.
Here is a summary of my current understanding:
My mind was blown when I read what Dr. Alan Kay said about the meaning of object-oriented programming:
So, basically, there are three fundamental things about OOP:
Some people may think class is the fundamental component of OOP. But if we think further, a class is just data and the behaviors related to that data. And here, behavior is just another expression for “message”.
2. Local retention, protection, and hiding of state-process
This process is basically encapsulation. Encapsulation is the principle of hiding data in a class until it’s ready to be used. When the data in a class is encapsulated into an object, the user of this object won’t need to know the data it holds, but only the messages the object responds to.
3. Late binding
Messages can be sent through different objects, thus the message-to-code binding can be delayed as much as you want. Late binding is when the method being called upon an object is done after the code has already been written (at runtime). Late binding can help us write readable code.
Also known as dynamic binding, late binding is the goal that object-oriented programming ultimately wants to achieve. Both messaging and encapsulation serve this purpose, as encapsulation can free you from thinking about the binding process completely. And almost all the techniques we are using in object-oriented programming — inheritance, delegation, composition, polymorphism, you name it — are aimed for this purpose, too.
With late binding, you can just send a message to an object and let it do its job. You won’t need to know what object responds to your message when you send it to an object. The response can happen anywhere: the object itself (within its singleton class), its mixins, its superclass, etc. This is a really helpful feature when you read code. If you can always think in the same abstraction layer, without the need to worry about what’s going on underneath, you can understand the whole project more easily.
Without late binding, we’d need to know almost everything else in this program. Unlike a CEO just leading the whole company towards a common goal, you would have to be a manager who understands everything that’s going on within the company. This can be a huge burden when reading code.
How to Design a Good Object-Oriented Program?
Object-oriented programming is not a new idea. It has already existed for more than half a century (Simula came out in 1967). I was actually quite surprised that most of the design problems in object-oriented programming have already been solved/explained in 1980s.
So, how do we design a good OOP?
1. Know the principles
First, you need to know the design principles. It would be impossible to recognize a well-designed application without first knowing the design principles. Among all the existing principles, I’ve found that the SOLID principle is the most powerful but the hardest to learn. I haven’t really seen any projects that follows the SOLID principle entirely, and I’m still finding my way to fully understanding these principles.
Here are some great resources for design principles:
• A series of video tutorials explaining SOLID on Upcase
Refactoring is the principle of restructuring code without changing any external behavior. There are several ways of refactoring code as good design practice:
Before adding a new feature
I often find myself adding a new feature by just adding a few lines of
if statements. And as time went by, the function would become too long, the class too large, and the whole project nearly unsustainable.
But if we refactor every time before we add a new feature, so that adding new feature can be easily done after the refactoring. The code will stay clean much longer.
After a feature is implemented
One thing I realized is that it’s nearly impossible to write a well-designed program on the first attempt.
Only when we finish the program can we gain a better understanding of the scope of the project on which we’re working and better rearrange the code.
Of course, this doesn’t only apply to features. It can also apply to functions, classes, and tests. After I finish any of them, I’ll go back and see if there is any way to make this code cleaner and more clear.
3. Unified Modeling Language (UML)
When I first learned about UML, I was fully unaware of the power of these tools. I thought, “these are just simple diagrams; what a waste of time to draw these.” But I was wrong.
They are actually quite helpful when we design or refactor our system.
UML allows engineers to draw diagrams and model classes, methods, etc., and actually, they are the cheapest way to do software design I’ve ever known.
They are important when we need to communicate over different design solutions.
They are a standard for teammates to communicate. It’s really hard to get on the same page if two people are using different graphical languages when they discuss things like modeling.
Most Important Takeaways for Object-Oriented Programming
- Embrace Change
Change is the biggest problem in software engineering.
If we are not going to change a program after its initial development, then fine. We can write it however we want, as long as it’s working.
But this will hardly ever be the case. We will always need to support different input/output. We will always need to tweak some algorithms. We will always need to add some new features.
With these inevitable changes in mind, we need to be agile — we need to embrace change and be prepared for them.
2. Code for Readability
I remembered this quote from Structure and Interpretation of Computer Programs:
Programs must be written for people to read, and only incidentally for machines to execute.
And there are many experienced programmers like Martin Fowler who advocate the same thing. A program is well-designed if another developer can easily read the code and debug or tweak it.
In other words, keep it simple!
Yiming Chen is a web developer for Ekohe passionate about code. His favorite comic book character is the Flash. Feel free to leave a comment or ask questions about programming (or anything else!).