General Principle
Practical example
Why it matters
Applying it in our codebase
Credited to Bertrand Meyer 🇫🇷
Popularized by Bob Martin (SOLID)
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
=
We should design our modules, classes and functions in a way that when a new functionality is needed, we should not modify our existing code but rather write new code that will be used by existing code.
Extend the behaviour without changing it.
# NOT OC
class Purchase
def charge_user!
charge_with_paypal
send_confirmation
end
def charge_with_paypal
verify_paypal_account
Paypal.charge(user: user, amount: amount)
end
end
# OC
class Purchase
def charge_user!
payment_processor.charge(user: user, amount: amount)
send_confirmation
end
end
class PaypalProcessor
def charge(user:, amount:)
# charge user /w Paypal logic
end
end
class StripeProcessor
def charge(user:, amount:)
# charge user /w Stripe logic
end
end
# ------ NOT OC -----
class NotificationSender
def send(user, message)
EmailSender.send(user, message) if user.active?
end
end
# ----- OC -----
class NotificationSender
def send(user, message, sender = EmailSender.new)
sender.send(user, message) if user.active?
end
end
class Sender
def send(user:, message:)
raise NotImplementedError
end
end
class EmailSender < Sender
def send(user:, message:)
# implementation for Email
end
end
class SmsSender < Sender
def send(user:, message:)
# implementation for SMS
end
end
sender = NotificationSender.new
sender.send(user, "Wesh gros", SmsSender.new)
# ------ NOT OC -----
module CoffeeShop
class Purchase
attr_reader :user
def initialize(user)
@user = user
end
def perform
user.give(hot_coffee)
user.charge(amount)
end
private
def hot_coffee
cup.fill(coffee)
cup
end
def cup
@_cup ||= PaperCup.new
end
def coffee
expresso = ExpressoMachine.make_expresso
expresso.add_sugar(pieces: 1)
expresso
end
def price
4
end
end
end