Open Closed Principle
The Open/Closed Principle, a principle of object-oriented programming, emphasises that classes or methods should be designed in a way that allows for extension (adding new functionality) without modification (changing existing code). This principle promotes the idea of designing code that is flexible, maintainable, and resilient to change.
The rationale behind the Open/Closed Principle is that modifying existing code can introduce risks of introducing bugs
or un-intensionally altering the behaviour of a system. Therefore, it is preferable to avoid modifying tested and reliable code whenever possible. Instead, the focus should be on extending the code to accommodate new functionality
, without altering the existing code.
To achieve this, modular designs that promote separation of concerns and encapsulation are often used. One common design pattern that aligns with the Open/Closed Principle is the Strategy Pattern
. The Strategy Pattern involves encapsulating algorithms or behaviours as separate classes, and allowing them to be swapped out at runtime. This way, new behaviours can be added or existing behaviours can be changed without modifying the core code of the system.
By following the Open/Closed Principle and designing code that is open for extension but closed for modification, developers can create code that is more maintainable, adaptable, and less prone to introducing unintended bugs. It promotes good coding practices and helps create software systems that are easier to evolve and extend over time.
In Ruby, classes are open, meaning that you can modify or extend them even after they are defined. However, it's important to be cautious when monkey-patching
(i.e., modifying) a class directly, as it can have unintended consequences and affect all the dependencies that call that class.
To adhere to the Open/Closed Principle in Ruby, it's generally recommended to prefer subclassing or using composition over directly modifying existing classes
. This way, you can create a new class or module that inherits from or collaborates with the original class, respectively, to implement changes or extensions, without modifying the original class itself.
Example:
class Order
def initialize
@items = []
@total_cost = 0
end
def add_item(item, price)
@items << [item, price]
@total_cost += price
end
def apply_discount(discount)
@total_cost -= discount
end
def total_cost
@total_cost
end
# Other methods for calculating total cost, generating order reports, etc.
end
class DiscountStrategy
def calculate_discount(total_cost)
raise NotImplementedError, "Subclasses must implement this method"
end
end
class TenPercentOffDiscount < DiscountStrategy
def calculate_discount(total_cost)
total_cost * 0.1
end
end
class FixedAmountDiscount < DiscountStrategy
def initialize(amount)
@amount = amount
end
def calculate_discount(total_cost)
total_cost - @amount
end
end
# Usage:
order = Order.new
order.add_item("Item 1", 100)
order.add_item("Item 2", 200)
puts "Total cost without discount: $#{order.total_cost}"
ten_percent_discount = TenPercentOffDiscount.new
order.apply_discount(ten_percent_discount.calculate_discount(order.total_cost))
puts "Total cost with 10% off discount: $#{order.total_cost}"
fixed_amount_discount = FixedAmountDiscount.new(50)
order.apply_discount(fixed_amount_discount.calculate_discount(order.total_cost))
puts "Total cost with $50 fixed amount discount: $#{order.total_cost}"
In this example, the Order
class has a DiscountStrategy
base class that defines a common interface for calculating discounts. The TenPercentOffDiscount
and FixedAmountDiscount
classes are implemented as subclasses of DiscountStrategy
and provide specific discount calculation logic.
The Order
class has a apply_discount
method that takes a discount value as an argument, which is calculated by calling the calculate_discount
method on the appropriate discount strategy object.
This allows for different discount strategies to be applied dynamically at runtime without modifying the Order
class. New discount strategies can be easily added by implementing a new subclass of DiscountStrategy
and providing the discount calculation logic in the calculate_discount
method, without modifying the Order
class.