Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mthongvanh/04fa6811975ff59fbd1fbfe5f41f3bcb to your computer and use it in GitHub Desktop.

Select an option

Save mthongvanh/04fa6811975ff59fbd1fbfe5f41f3bcb to your computer and use it in GitHub Desktop.
gemma3-4b explains the liskov principle

Okay, let's draw a parallel between the Liskov Substitution Principle (LSP) and finance. It’s about ensuring that if you can replace a base class object with one of its subclasses without changing the code that uses it, your system remains stable and predictable.

The Core Idea of LSP:

LSP states that if a subclass should be able to replace its parent class in any context, it must behave identically to the base class in all relevant situations. This means that if you have a function expecting an Account object, it should work equally well with a SavingsAccount or a CheckingAccount (assuming they both inherit from the Account class).

The Financial Analogy: Investment Accounts

Let's use the concept of investment accounts as our financial analogy.

  • Base Class (Account): Think of a generic “Investment Account” as the base class. This represents a general concept – any account that holds investments and has associated actions like depositing, withdrawing, and displaying balance.

  • Subclasses (SavingsAccount, CheckingAccount, BrokerageAccount): These are specific types of investment accounts:

    • Savings Account: Holds savings and earns interest.
    • Checking Account: Used for everyday transactions.
    • Brokerage Account: Holds stocks, bonds, and other securities.

How LSP Applies in Finance (and Software):

Let's consider a scenario: You have a system for managing investment accounts. You’ve written code that operates on any InvestmentAccount. This code might look like this (simplified):

# Example in Python - Illustrative, not production-ready
class InvestmentAccount:
    def __init__(self, balance):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
        else:
            print("Insufficient funds!")

    def get_balance(self):
        return self.balance


class SavingsAccount(InvestmentAccount):
    def __init__(self, balance, interest_rate):
        super().__init__(balance)  # Call the parent class's constructor
        self.interest_rate = interest_rate

    def calculate_interest(self):
        return self.balance * self.interest_rate


class CheckingAccount(InvestmentAccount):
    def __init__(self, balance):
        super().__init__(balance)

# Code that works with any InvestmentAccount:
def process_investment(account):
    print("Processing account:")
    print(f"Balance: {account.get_balance()}")
    account.deposit(100)
    print(f"New balance: {account.get_balance()}")


# Now, let's test with a Savings Account
savings = SavingsAccount(1000, 0.05)
process_investment(savings)

# Let's try with a Checking Account:
checking = CheckingAccount(500)
process_investment(checking)

What happens if we don't follow LSP?

Let’s imagine a flawed approach where we try to modify process_investment to handle only CheckingAccounts:

# BAD EXAMPLE - Violates LSP!
def process_checking(account):  # Only works with CheckingAccounts
    print("Processing checking account:")
    print(f"Balance: {account.get_balance()}")
    #  We can't add a 'calculate_interest' method here because it doesn't make sense for a checking account.
    #  This breaks the principle - we can't replace a CheckingAccount with an InvestmentAccount.

Why is this bad?

  • Breaks Flexibility: If we later introduce a new type of investment account (e.g., a Roth IRA), we must change process_checking to handle it as well. This is brittle and makes the code harder to maintain.
  • Loss of Polymorphism: The original process_investment function is now limited in its functionality. It can't take any investment account – only CheckingAccounts.
  • Increased Complexity: We end up with duplicated code or complex conditional logic to handle different account types.

The Correct Approach (Following LSP):

Instead of modifying process_investment, we should design it to work with any InvestmentAccount. If a new account type needs special handling, we add that functionality to the specific subclass (e.g., SavingsAccount might have a calculate_interest() method).

The Financial Parallel:

  • LSP is like a standardized investment strategy. You want to be able to apply the same general approach (e.g., "invest a portion of your income") across different investment vehicles (stocks, bonds, mutual funds).
  • Subclasses are like specific investment products. Each product has its own unique characteristics (risk, return), but they all adhere to the underlying investment strategy.
  • Violating LSP is like trying to force a single investment strategy onto every asset. It’s not effective and can lead to poor results.

In summary, LSP encourages us to design our software with flexibility and extensibility in mind – just like a good financial advisor would adapt their advice to your individual circumstances.

Do you want me to elaborate on any specific aspect of this explanation, such as:

  • How LSP relates to inheritance?
  • How it connects to design patterns (like Strategy)?
  • Give you an example in a different programming language?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment