OOP Untangled Part 1: Object-Orientation and Dynamic Languages
If you are a programmer, it means that you are already familiar with the term OOP; if you are not, Object-Oriented Programming (OOP) is a programming paradigm where you write and organize your code in a specific style. However, there are various definitions of the object-orientation, here I am biased to one of Alan Kay’s—the person who coined the term, thus, it will be the definition this article will focus on.
This article is part 1 of a 3 articles series. Thanks to the feedback I got from friends and teachers, I’ve decided to split one article into a 3 part series that you’re reading at the moment.
This article will not focus on concepts like Inheritance or Composition, this is mainly focused on messaging—the most important part of OOP according to Alan Kay.
Most of the parts of the article are my opinions and from my understanding of OOP at the present time. I used to have an entirely different understanding 4 years ago. I might change my understanding after few years. That’s ok. We always keep learning and adapt as a new concept emerges.
This article is not related to computer performance, it’s more related to people’s.
Who is Alan Kay?
If you don’t know him, he’s best known for his work on GUI, Smalltalk—one of the earliest Object-Oriented languages, and he’s the first who invented the term Object-Oriented Programming. Although, the tech industry has taken a different approach to what he envisioned back then.
So, how did he define the Object-Orientation?
According to the email conversation recorded here, Alan Kay states the concept of Object-Oriented Programming as the following:
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I’m not aware of them.
In other words, in Object-Oriented Systems, you have a net of objects that exchanges messages with one another—without exposing the state (encapsulation) and extreme late-binding (at runtime).
This is interesting, Alan Kay seems to emphasize messaging rather than the objects.
Any time you have dealt with objects, you should have already used messaging. But how is that? Each time you call a “method” on an object, you’re sending a message to that object. The object then, either responds to your message or fails.
To visualize that, see the diagram below.
As you can see, there’s an object (could be any object, or even you using some REPL), you are sending an area message to an object called “circle” that object might also send another message to a function that squares a number (not to be confused with the square shape). If everything works well and all objects respond to the messages sent to them. You will get the value of 12.56 when you pass 2—because it calculates the area of the circle (area formula is π r^2).
However, some details that are unnecessary, for example, the shape could be a square (or an elephant) instead of a circle, in this case, it will return a different result, but as long as it responds to the area message our program will be still valid. Though, good naming of messages is critical here.
The Other Concepts: Hiding State-Process and Late-Binding
He states that we should protect and hide the state-process. This implies preventing anything outside the object from modifying the data of the object. Thus, the state is only computable via the messages. Each time you send a message. The object might respond and change its internal state. In other words, when writing the classes, we should keep all fields private, and the only things that can be public are the methods. Some languages like Ruby, have all fields (instance variables) inaccessible outside of the object, only methods (includes setters and getters) can be public. Other languages like Python keep the fields accessible from the outside world.
When you hear about objects, you should always think about runtime. You write classes, which will get parsed at compile-time. But when you create objects from them, it happens at runtime. The same thing with objects sending messages among themselves; it all happens at runtime. When object X sends a message to object Y, X will not have any clue what code will be executed—if any. Because at that time the message might not be bound to the method. This actually is the default behavior in dynamic languages. Some static languages (like Java, C# …etc) achieve this late-binding via polymorphism (will talk about this later in a future article).
An Example in a Dynamic Language
Dynamic languages make it effortless to implement the OOP like what Alan Kay envisioned. Since everything will be bound at runtime—a.k.a “late-binding of all things”. Add to that, the low-ceremony nature those languages have when it comes to creating objects (and classes).
If you know some Ruby, you will notice that all objects already support a message called send and another one called respond_to? the first sends a message to the object and the other checks if the object can respond to the message.
42.send :to_s # Sends a :to_s message 42.send :send, :to_s # Yes, you can send a :send message 42.respond_to? :to_s # => true 42.respond_to? :send # => true 42.respond_to? :foo # => false
In my current job, I had to make some integrations with 3rd party accounting software to sync some financial records. We needed to integrate with Xero and QuickBooks apps at the beginning and we will need to add more in the future.
To think about the problem in the OO world, it’s simply an object that should respond to a message called save_record. No need to worry about anything else and keep all implementation details hidden.
To implement this in Ruby, the code will look like this:
class XeroIntegration def save_record(record) # the logic for actually saving the record end # ... Other methods go here end class QuickBooksIntegration def save_record(record) # the logic for actually saving the record end # ... Other methods go here end # In another part of the world: integration = somehow_get_integration_by_id(id) integration.save_record record # Who cares which it is? It responds!
As you can see, I chose the name of the message carefully—save_record. We could rename it to save_record_to_xero and save_record_to_quickbooks respectively. But with this, we will lose the flexibility the OOP gives us and will result in a leaky abstraction—why name it very concretely when you can delegate that detail to the classes’ name?
Also, you notice that we didn’t use any form of inheritance—we would use it for reusability if needed—because what we want is an object that can respond to the message, rather than a hierarchy of classes. This is a fundamental distinction between the OOP in its early days, and the “OOP” we do in the modern languages we have today.
One final note, I deliberately exposed one message to the outside world, it’s possible to have way more messages. However, it’s a good practice to expose the bare minimum of them to the surface. Here I am implicitly implementing the Interface Segregation Principle of SOLID (will be discussed in later articles). Imagine the case where you need to write 10 different methods to have a drop-in replacement of the integration (or have a Test Double). Compare that to implementing/faking only one or two methods.
In the paragraphs above, I used both the terms message and method. They are not identical; Yet very related. The method contains the concrete details of the functionality you want to do (i.e. when you write the class), then, when you instantiate (create an object from) a class, the messages you send to the objects will be bound back to the methods of their respective classes. Remember that we always heard that “Class is a blueprint of the object”.
In part 1, we introduced the OOP as a concept as it was defined by Alan Kay. We implemented it as simply as possible using Ruby language. We know that we don’t need inheritance and other practices to implement OOP. We can achieve object-orientation by having anything responds to a message (or fail).
Although this part covered the dynamic type system, we can implement OOP in different systems, including the statically typed ones. This will be the subject of a future article.