If you haven't read the original challenge or the functional solution go ahead and give those a read before coming back here. Don't worry, I'll wait.
Now that you're back, let's write FizzBuzz (without if
s) in an Object-oriented style!
But what is Object-oriented?
Well, its just being oriented around objects, obviously!
Alright, that's not helpful. Here's the answers you'll usually hear:
- Encapsulation
- Abstraction
- Inheritance
- Composition
- Polymorphism
Abstraction can easily be done outside of OOP, as can encapsulation. We already mentioned "composition" in our functional approach. And polymorphism was the crux of solving FizzBuzz without if
statements (defining functions with the same signature but different behavior).
So the only thing on this list that's truly unique to OO is inheritance, and that's now widely regarded as over-used and harmful in most situations.
And we're left with the same question as when examining Functional programming: what really is the heart?
Looking back to the history of OOP the answer is simply this: objects passing messages to one another.
It has nothing to do with classes or inheritance; simply message passing between objects of varying implementation.
How to use it
Most of what you've written probably isn't OOP (I know mine isn't). My experience with "objects" has mostly been procedural programming using "objects" to store basic state at best or grouping procedures at worst.
So how could we use the concept of message passing to implement conditionals?
Similarly to our functional approach, we lean on differences in implementation.
- When the object is
true
, return one piece of internal state (previously set) - When the object is
false
, return another piece of internal state (previously set)
const baseBoolean = {
setThen: function(then) { return { ...this, then }; },
setOtherwise: function(otherwise) { return { ...this, otherwise }; },
};
const objectOrientedTrue = {
...baseBoolean,
evaluate: function() { return this.then; },
};
const objectOrientedFalse = {
...baseBoolean,
evaluate: function() { return this.otherwise; },
};
I've chosen to make my objects immutable (that is, every "change" really just returns a new object). This is not necessary to demonstrate OOP; but I think it's usually a good idea for code safety (remember Pure functions from Functional land?).
What we've done to our booleans, let us do to our numbers as well:
const objectOrientedNumber = {
value: 0,
isaMultipleCache: [objectOrientedFalse],
setValue: function(n) { return { ...this, value: n, isaMultipleCache: [objectOrientedTrue, ...Array(n).fill(objectOrientedFalse)] }; },
isaMultipleOf: function(dividend) { return this.isaMultipleCache[dividend.value % this.value]; }
};
(The isaMultipleOf
method uses the same trick that I explained in the functional solution)
And this is all we need to implement a FizzBuzz:
const objectOrientedFizzBuzz = {
for: function(n) {
const number = objectOrientedNumber.setValue(n);
return this.three
.isaMultipleOf(number)
.setThen(
this.five
.isaMultipleOf(number)
.setThen("FizzBuzz")
.setOtherwise("Fizz")
.evaluate()
)
.setOtherwise(
this.five
.isaMultipleOf(number)
.setThen("Buzz")
.setOtherwise(number.value)
.evaluate()
)
.evaluate();
},
three: objectOrientedNumber.setValue(3),
five: objectOrientedNumber.setValue(5),
};
If we squint we can see the same basic structure we used in our functional approach, just much more verbose! (you can see both solutions below in the codepen)
Follow me for the last post in this series where I'll do a comparison of FP and OOP; I'll see you there!
Top comments (3)
Hy Nathan,
You mentioned 5 principles of OO:
but which one applies to "objects passing messages to one another"?
I´ve done several projects extensively using OO methods, but none of this used a messaging infrastructure for internal communication. There have been some approaches in the past (like Smalltalk), but a.f.a.i.k non of the current OO approaches does rely on this.
For me, OO is simply a way to organize your code.
Let´s take an example: Implement a clock using HTML, CSS and Javascript :-). It is easy to put things together for a website. But what, if you need two or more clocks showing different time? Building the clock as a class guarantees, that codes are encapsulated, they will not have any unwanted effects outside the class.
Why can we be so sure to have no side effects? Becaus a class in an abstraction, it does not implement any real code. You need to create instances of the class to get a working program.
Now, we have built several classes for our project, but we want to implement a messaging infrastructure. Our objects should be able to talk to each other using messages. So, we need a common mechanism to exchange messages, let say, each object sould have a procedure talk() and listen(). Maybe there are tools needed to generate and analyze messages. Or a common dictionary. So, how do we implement this?
We want all our objects to have the ability to communicate, to share the same methods of communication. We can do this by implementing this methods for each new class again, which is very prone to errors. What if we change the common languange? Then we have to change all our classes. It is far easier to put all the common stuff in a base class and derive all functional classes from this base. As the base class does nothing useful itself (it can only communicate), it will probably never be instantiated, so we call this an abstract class.
Using OO this way is all about maintainability of code. Inheritance and polymorphism are necessary to let you implement a code only once, encapsulation and abstraction are necessary to avoid side effects. And a well designed object hierarchy allows you to make big changes to your program without getting into trouble. You may - for example - rebuild a complete database adapter for a banking application. As long as you do not change the interface of your object, you can be pretty sure the rest of your app will not even recognize.
As a paradigm, OO is often enough misunderstood. It may be not very helpful for problems with 20 lines of code. But if you need to maintain large applications with 100.000 lines of code, you will be happy to have separate islands of code you can replace or update without getting into trouble.
I think my point is that they don't (directly). Perhaps I could have been more clear. These are the five I've commonly heard when reading about OO. Like you've said, I think "OO is often enough misunderstood".
I'm sorry, I'm now realizing I could have been much more clear in my article about what I mean when I say "messaging". I do not mean to conflate it with event-driven or PubSub patterns. Those are two uses of messages, but don't (in my opinion) cover all messages.
At the most basic, a message consists of a name and parameters being sent to a particular object. Depending on how the object was constructed (from a class definition, from metaprogramming, from prototypes a la JavaScript) will determine what names of messages and what structure of parameters per name this object is capable of handling (any messages of an unknown name or poor structure should be considered an error). How do we usually define the names, parameters, and behavior for the messages? We use methods! To quote Wikipedia: "A method ... is a procedure associated with a message and an object." You can't not have done messaging in OO, because objects can't do anything besides receive messages.
We don't need inheritance to avoid duplicating code, just look at the way I got
baseBoolean
behavior into bothobjectOrientedTrue
andobjectOrientedFalse
. That's not inheritance, I would say that's excellent composition support (I get the common functionality on my object, I can override it, and I could still get other common functionality on my object without worrying about being tied to inheriting from a single class with complete control over what should override what when confronted with name conflicts).Looking back, almost every time I've used inheritance I would have been better served from either composition or from an interface definition separate from any class/method definitions (or a combination of the two). The main issue with inheritance is that it locks us into always pulling both behavior and interface and (in most languages) we can only inherit from one other class.
I'll take your example of rebuilding the database adapter for a banking app. Let's say we've historically had two databases: one old Relational DB and one to store JSON blobs. Being so different we had two adapters, one to interact with Old Relational DB and one for JSON. But hey, there's this new database that's both relational and capable of storing our JSON directly in the tables, that sounds nice...
...So we begin writing a single new adapter to replace the two old adapters. But with only single inheritance, I have a terrible choice: either duplicate some code across two classes to maintain the two interfaces or break the interface for one of my two use cases (forcing huge refactors across the whole codebase). Composition would let me pull in common behavior to two classes (one for each interface). Proper interfaces would let me have one class (containing the common behavior) and properly declare myself as implementing both interfaces the old adapters provided.
Luckily there are different ways to catch a tiger... If you find other methods more useful, why did you use inheritance then? It is an option, not an obligation.
I´m not sure if I would want to put the database adapter in a parent class. You do not need to inherit something to work with a class. You can just define an interface (for example a messaging system) and ask the database....
But maybe inheritance is a good way to define a messaging system for all your objects (ok, that was already mentioned :-)