SOLID: Liskov Substitution Principle in Ruby


Created by Stan on 18-04-2023


The Liskov Substitution Principle (LSP) is one of the five principles of object-oriented programming and design known as SOLID. It was introduced by Barbara Liskov and states that objects of a derived class should be able to replace objects of the base class without affecting the correctness of the program. In this article, we will explore the LSP and demonstrate its application in Ruby using a real-world example.

Understanding the Liskov Substitution Principle

The LSP helps to ensure that a subclass can always be substituted for its superclass without affecting the overall behavior of the program. It guarantees that a derived class will maintain the same contracts as its base class, including method signatures, return types, and expected behavior.

Adhering to the LSP ensures that your code is robust, maintainable, and easy to understand. When a class hierarchy follows the LSP, it becomes much simpler to reason about the behavior of individual classes and their relationships.

Applying the Liskov Substitution Principle in Ruby: Payment Processing Example

Consider an e-commerce application that supports multiple payment methods, such as credit cards and PayPal. We can use the LSP to create a flexible and maintainable design for processing payments.

class PaymentProcessor
  def process(payment_method, amount)
    payment_method.charge(amount)
  end
end
class CreditCard
  def charge(amount)
    puts "Charging credit card with amount: #{amount}"
    # Code to charge the credit card
    # In a real-world scenario, this would involve API calls to the payment gateway
    true
  end
end
class PayPal
  def charge(amount)
    puts "Charging PayPal account with amount: #{amount}"
    # Code to charge the PayPal account
    # In a real-world scenario, this would involve API calls to the PayPal API
    true
  end
end

In this example, we have a PaymentProcessor class that accepts a payment_method object and an amount as arguments. The payment_method object should have a charge method that takes the amount as an argument. Both the CreditCard and PayPal classes implement this charge method, allowing them to be used interchangeably as payment methods.

This design adheres to the LSP, as the CreditCard and PayPal classes can be substituted for each other without affecting the correctness of the program.

Now let's test the code.

payment_processor = PaymentProcessor.new
credit_card = CreditCard.new
paypal = PayPal.new

payment_processor.process(credit_card, 50)
payment_processor.process(paypal, 50)

Violating the Liskov Substitution Principle: Introducing a Gift Card Payment Method

At first glance, this implementation might seem reasonable, as a gift card requires an additional piece of information (a PIN) to process a payment. However, it violates the LSP because the GiftCard class doesn't maintain the same contract as the other payment method classes.

If we try to use the GiftCard class with the existing PaymentProcessor, the program will raise an error due to the mismatch in method signatures:

payment_processor = PaymentProcessor.new
gift_card = GiftCard.new

payment_processor.process(gift_card, 50) # Raises an ArgumentError: wrong number of arguments

Fixing LSP Violations

To fix the LSP violation, we can modify our class hierarchy to ensure that the contracts are maintained. In this case, we can update the GiftCard class to accept the PIN in its initializer, allowing it to maintain the same charge method signature as the other payment methods:

class GiftCard
  def initialize(pin)
    @pin = pin
  end

  def charge(amount)
    puts "Charging gift card with amount: #{amount}, using PIN: #{@pin}"
    # Code to charge the gift card
    # In a real-world scenario, this would involve API calls to the gift card provider
    true
  end
end

Now, the GiftCard class follows the LSP and can be used interchangeably with the other payment methods:

payment_processor = PaymentProcessor.new
gift_card = GiftCard.new(1234)

payment_processor.process(gift_card, 50)


Related Posts