After learning what the Law of Demeter is really about, I wanted to go further in Object-Oriented Design with the well known S.O.L.I.D principles. Because we only truly learn something when we try to teach it, today I'm sharing with you what I learned about the O in S.O.L.I.D.

Open/Closed principle

Objects should be open for extension but closed for modification. It means that we should be able to add new behaviors to an object, without touching existing code. It seems paradoxical, right ? How can we implement new functionalities without modifying existing code ?

Let's dive into an example to try to clarify this Open/Closed story.

Imagine you have a great app and you want to send a notification every time a new order is made. You have a pretty straightforward class Notifier, with a method notify_for_new_order that creates a Notification with a message.

class Notifier
  attr_reader :order

  def initialize(order)
    @order = order

  def notify_for_new_order
    Notification.create(message: "Hey, you've got a new order !")

The piece of code above seems reasonable. For an application that plans not to grow, maybe it is better to keep this as it is. But if you think your application will grow (and by growing I mean changing), maybe you should view things differently.

Who knows what ?

Why does Notifier knows about the message you plan to send for a new order ? Why does Notifier knows about Order at all ? Let's ask ourselves the right question : what Notifier wants ?

It just wants to notify.

So instead of coupling Notifier with Order, maybe we could put an object that can act like an order. What is the particularity of Order that we want to keep here ? The only thing that we need to know about order is that it is notifiable, wich means we can notify it (in Slack, by mail, or whatever).

If it looks like a duck, quacks like a duck, then it's a duck!

To improve our code, we will use something called Duck Typing. It's a funny name for a really powerful concept in Obect-Oriented Design. Duck typed objects are defined by their behavior rather than their class. Let's go back to our example and make a few changes to understand how this duck typing thing can help us :

class Notifier
  attr_reader :notifiable

  def initialize(notifiable)
    @notifiable = notifiable

  def notify
    Notification.create(message: notifiable.notification_message)

class Order
  def notification_message
    "Hey, you've got a new order !"

We did not change a lot of things here. We just removed the order reference in Notifier by including a notifiable object (the duck typed object), and added a notification_message method in Order.

But was it an improvement ? Yes, it was.

Now, Notifier is not tight to Order : you just need to give it an object that responds to notification_message, which means an object that is notifiable. We now focus on the behavior of the object rather than its class.

"Ok, but it seems to add complexity and we did not gain so much from it", you might tell yourself.

Where the magic happens

But now imagine you want to send a new notification each time you have a new subscriber. The only thing you need to do is making your subscriber "notifiable" by adding :

class Subscriber
  def notification_message
    "You've got a new subscriber !"

And now, when you create a new subscriber, just do :

Did you just see what happened here ?!

We added a new behavior to our Notifier class without touching it. That's what the Open/Closed principle is all about. Notifier is now open for extension but closed for modification.

Powerful, isn't it ?

Let me know your thoughts on the subject and keep learning ! smile

#object-oriented #design #ood #ruby #open-closed