Code Review Ep 2: Liskov’s Substitution Principle
Hello fellow developers! Welcome back to another weekly code review on my blog. This week, we’ll be discussing Liskov’s Substitution Principle (LSP) and how it can be violated in code. LSP is one of the five SOLID principles and is crucial for writing maintainable and extensible object-oriented code.
Let’s dive right into the code review. Our colleague, Alex, sent us the following code that demonstrates a violation of Liskov’s Substitution Principle:
class Currency {
let code: String
let symbol: String
init(code: String, symbol: String) {
self.code = code
self.symbol = symbol
}
}
class CurrencyFormatter {
func format(amount: Double, currency: Currency) -> String {
// Format the amount using the appropriate currency symbol (Not implemented here).
return "\(currency.symbol)\(amount)"
}
}
class FixedRateCurrencyFormatter: CurrencyFormatter {
let exchangeRate: Double
init(exchangeRate: Double) {
self.exchangeRate = exchangeRate
}
// Violation of LSP - Overrides base class with fixed exchange rate
override func format(amount: Double, currency: Currency) -> String {
guard currency.code == "EUR" else {
fatalError("FixedRateCurrencyFormatter only supports formatting amounts in EUR.")
}
let convertedAmount = amount * exchangeRate
return "\(currency.symbol)\(convertedAmount)"
}
}
let usd = Currency(code: "USD", symbol: "$")
let eur = Currency(code: "EUR", symbol: "€")
let currencyFormatter: CurrencyFormatter = FixedRateCurrencyFormatter(exchangeRate: 0.85)
let formattedAmount = currencyFormatter.format(amount: 100.0, currency: usd)
print("Formatted amount: \(formattedAmount)")
Our colleague has implemented currency formatting classes — Currency
, CurrencyFormatter
, and FixedRateCurrencyFormatter
. The base class CurrencyFormatter
handles general currency formatting, while the subclass FixedRateCurrencyFormatter
specializes in formatting amounts using a fixed exchange rate but only for EUR currency. Now, let's identify the violations of Liskov's Substitution Principle in this code:
Violation : Contradictory Behavior in FixedRateCurrencyFormatter
In LSP, derived classes should not change or contradict the behavior of the base class in a way that might lead to unexpected results when substituting objects. In this code, the FixedRateCurrencyFormatter
class contradicts the behavior of the base class CurrencyFormatter
. The base class is designed to handle general currency formatting, but the FixedRateCurrencyFormatter
only supports formatting amounts for EUR currency with a fixed exchange rate.
Correction: Applying Liskov’s Substitution Principle
To correct this violation and adhere to Liskov’s Substitution Principle, we need to ensure that the FixedRateCurrencyFormatter
can be used interchangeably with the base class CurrencyFormatter
without introducing constraints. We should modify the FixedRateCurrencyFormatter
to support formatting amounts in any currency using a dictionary of exchange rates.
class CurrencyFormatter {
func format(amount: Double, currency: Currency) -> String {
// Format the amount using the appropriate currency symbol (Not implemented here).
return "\(currency.symbol)\(amount)"
}
}
class FixedRateCurrencyFormatter: CurrencyFormatter {
let exchangeRates: [String: Double]
init(exchangeRates: [String: Double]) {
self.exchangeRates = exchangeRates
}
override func format(amount: Double, currency: Currency) -> String {
guard let exchangeRate = exchangeRates[currency.code] else {
fatalError("FixedRateCurrencyFormatter does not have an exchange rate for \(currency.code).")
}
let convertedAmount = amount * exchangeRate
return "\(currency.symbol)\(convertedAmount)"
}
}
let usd = Currency(code: "USD", symbol: "$")
let eur = Currency(code: "EUR", symbol: "€")
let exchangeRates: [String: Double] = ["USD": 0.85, "EUR": 1.0, "GBP": 1.20]
let currencyFormatter: CurrencyFormatter = FixedRateCurrencyFormatter(exchangeRates: exchangeRates)
let formattedAmountUSD = currencyFormatter.format(amount: 100.0, currency: usd)
print("Formatted amount in USD: \(formattedAmountUSD)")
let formattedAmountEUR = currencyFormatter.format(amount: 100.0, currency: eur)
print("Formatted amount in EUR: \(formattedAmountEUR)")
By making this correction, our code now adheres to Liskov’s Substitution Principle. The FixedRateCurrencyFormatter
can be used interchangeably with the base CurrencyFormatter
and can format amounts for any currency using the provided exchange rates.
And there you have it! A simple but important demonstration of how adhering to Liskov’s Substitution Principle helps in creating maintainable and flexible software systems. Keep coding responsibly, and stay tuned for more exciting topics in the world of software development.
Happy coding! 🚀