OOP Untangled Part 2: Object-Orientation and Statically Typed Languages

By Mohammed A.
May 16, 2021 • 6 mins read
OOP Untangled Part 2: Object-Orientation and Statically Typed Languages

In the first part, we tried to understand the Object-Orientation according to Alan Kay’s definition, and we gave examples in dynamically typed languages. In this part, we try to implement something similar but in some statically typed languages like Swift and Kotlin and see how different it is from the traditional (i.e. C++) OOP that we used to do with those languages.

This is the 2nd part of a series of articles, to check the other parts:

OOP Untanged Part 1: Object-Orientation and Dynamic Languages

Implementation In A Static Language

Many of the statically typed languages these days claim that they are object-oriented. Although, they are taking a different approach to the OOP first introduced by Alan Kay in Smalltalk. While it’s not certain if Alan Kay put dynamic typing as one of the conditions of Object-Orientation. Let’s assume that it’s not—and try to do some OOP using a statically typed language.

I like what Scott Wlaschin said about making fun of “Enterprise OO” in one of his talks;

Making fun of Enterprise OO is like shooting fish in a barrel

Or

Making fun of Enterprise OO is like shooting IMarineVertebrates in an AbstractBarrelProxyFactory

This is fair enough, OO these days are centered around classes and types, thus making it tedious to learn and difficult to implement in everyday programming. The meme below is funny and sad at the same time. Look how you might end up doing if you continue doing OOP in that manner:

World Seen by an Object-Oriented Programmer Image

Come on, we can do better than that. And yes, there are tons of big enterprise-y apps that use OOP in that fashion. However, if it works, it doesn’t mean it’s the right way.

Enough memes; let’s write some code. I will write the same solution I implemented in Ruby above, but now in both Kotlin and Swift—as two modern languages we have today.

interface Integration {
  fun saveRecord(record: Record)
}

class XeroIntegration : Integration {
  override fun saveRecord(record: Record) {
    // ... implementation goes here
  }
  // ... more methods
}

class QuickBooksIntegration : Integration {
  override fun saveRecord(record: Record) {
    // ... implementation goes here
  }
  // ... more methods
}

// In another place in the world
val integration = somehowGetIntegrationById(id)
integration.saveRecord(record)
protocol Integration {
    func saveRecord(_ record: Record)
}

class XeroIntegration: Integration {
    func saveRecord(_ record: Record) {
        // ... implementation goes here
    }
    // ... more methods
}

class QuickbooksIntegration: Integration {
    func saveRecord(_ record: Record) {
        // ... implementation goes here
    }
    // ... more methods
}

// In another place in the world
let integration = somehowGetIntegrationById(id)
integration.saveRecord(record)

The code has the same functionality as the Ruby code above with additional building blocks—the interface/protocol—and we have to implement those in our classes. Keep in mind that we can use interface implementation to achieve polymorphism (late-binding).

It’s deliberate that we used those languages because they use different terms to describe the same thing—Interfaces and Protocols. This is important and insightful. We all know HTTP—the protocol of the web. It’s just a set of rules between 2 parties—like all protocols. Those rules include how the messages of HTTP should be formed.

And guess what! In OOP we also have messages! In fact, I think that using interfaces/protocols are well suited with OOP in static languages; because they set some rules of the messages that are supported in the objects that implement them—in addition to their late-binding.

There’s one trade-off here. The good side is that we have a validation of the messages this object support at the time we write the code. And the bad side is that if we are getting an integration object that supports the same message—saveRecord—from someone else’s code we have to wrap their object in a class that implements our interface, or we extract a package that has the interface and share it among our team. Both seem to add extra work, but they work eventually.

In fact, those statically typed languages do late-binding as polymorphism, but I am not sure if this is an “extreme late-binding” or not.

Does This Mean That Dynamic Languages Are Better?

Well, I think each paradigm has its own trade-offs. For example, in statically typed languages, the IDE can be of help in IntelliSense and refactoring. While in dynamically typed languages this is a more complex task since the types are only known at runtime. Although, some dynamic languages have some optional static type checking like Ruby (sorbet, rbs), Python, JavaScript (TypeScript), and PHP just to name a few.

On the other side, dynamic typing is more flexible and forgiving when it comes to checking the types, that’s why we have duck typing and this is not easy with static typing (Generics might help).

My favorite quote about this subject is from Structuring and Interpretation of Computer Programs

Pascal [statically typed] is for building pyramids–imposing, breathtaking, static structures built by armies pushing heavy blocks into place. Lisp [dynamically typed] is for building organisms–imposing, breathtaking, dynamic structures built by squads fitting fluctuating myriads of simpler organisms into place. …… As a result the pyramid must stand unchanged for a millennium; the organism must evolve or perish.

Conclusion

In part 2, we implemented OOP according to Alan Kay’s definition but this time with some statically typed languages. We didn’t need class inheritance to have object orientation, instead, interfaces/protocols were perfect for defining the message that the object can respond to.

This part covered the implementation in 2 modern well-known programming languages, however, there are other interesting languages that are worth mentioning in a future article.