Practice Problems for Liskov Substitution Principle
Liskov Substitution Principle (LSP)
Practice Problems
To master the Liskov Substitution Principle, it's essential to engage in practical exercises and projects. Here are some complete coding exercises, sample projects, quizzes, and multiple-choice questions (MCQs) to help reinforce your understanding.
Code Refactoring Exercise
Given:
- Java
- JavaScript
- Python
class Bird {
public void fly() {
System.out.println("Flying");
}
}
class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Ostriches can't fly");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Ostrich();
bird.fly(); // Throws UnsupportedOperationException
}
}
class Bird {
fly() {
console.log('Flying')
}
}
class Ostrich extends Bird {
fly() {
throw new Error("Ostriches can't fly")
}
}
const bird = new Ostrich()
bird.fly() // Throws Error: Ostriches can't fly
class Bird:
def fly(self):
print("Flying")
class Ostrich(Bird):
def fly(self):
raise NotImplementedError("Ostriches can't fly")
bird = Ostrich()
bird.fly() # Raises NotImplementedError: Ostriches can't fly
Challenge: Refactor the code to adhere to LSP.
Refactored:
- Java
- JavaScript
- Python
abstract class Bird {
public abstract void move();
}
class FlyingBird extends Bird {
@Override
public void move() {
fly();
}
public void fly() {
System.out.println("Flying");
}
}
class NonFlyingBird extends Bird {
@Override
public void move() {
walk();
}
public void walk() {
System.out.println("Walking");
}
}
class Sparrow extends FlyingBird {
// Sparrow inherits fly method and move behavior
}
class Ostrich extends NonFlyingBird {
// Ostrich inherits walk method and move behavior
}
public class Main {
public static void main(String[] args) {
Bird sparrow = new Sparrow();
Bird ostrich = new Ostrich();
sparrow.move(); // Outputs "Flying"
ostrich.move(); // Outputs "Walking"
}
}
class Bird {
move() {
throw new Error('This method should be overridden')
}
}
class FlyingBird extends Bird {
move() {
this.fly()
}
fly() {
console.log('Flying')
}
}
class NonFlyingBird extends Bird {
move() {
this.walk()
}
walk() {
console.log('Walking')
}
}
class Sparrow extends FlyingBird {
// Sparrow inherits fly method and move behavior
}
class Ostrich extends NonFlyingBird {
// Ostrich inherits walk method and move behavior
}
const sparrow = new Sparrow()
const ostrich = new Ostrich()
sparrow.move() // Outputs "Flying"
ostrich.move() // Outputs "Walking"
class Bird:
def move(self):
raise NotImplementedError("This method should be overridden")
class FlyingBird(Bird):
def move(self):
self.fly()
def fly(self):
print("Flying")
class NonFlyingBird(Bird):
def move(self):
self.walk()
def walk(self):
print("Walking")
class Sparrow(FlyingBird):
# Sparrow inherits fly method and move behavior
pass
class Ostrich(NonFlyingBird):
# Ostrich inherits walk method and move behavior
pass
sparrow = Sparrow()
ostrich = Ostrich()
sparrow.move() # Outputs "Flying"
ostrich.move() # Outputs "Walking"
Design Challenge
Challenge: Design a class hierarchy for a library management system that
adheres to LSP. The system should have base classes for Item
, with subclasses
like Book
, Magazine
, and DVD
. Each subclass should implement behaviors
appropriate to the type of item.
Coding Exercises
- Exercise 1: Implement an Animal Hierarchy:
- Description: Create a base class
Animal
with a methodmakeSound()
. SubclassesDog
,Cat
, andBird
should implement themakeSound
method. Ensure that replacing anAnimal
with any subclass does not change the expected behavior. - Example:
- Description: Create a base class
- Java
- JavaScript
- Python
abstract class Animal {
public abstract void makeSound();
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
class Bird extends Animal {
@Override
public void makeSound() {
System.out.println("Chirp");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();
dog.makeSound(); // Outputs "Bark"
cat.makeSound(); // Outputs "Meow"
bird.makeSound(); // Outputs "Chirp"
}
}
class Animal {
makeSound() {
throw new Error('This method should be overridden')
}
}
class Dog extends Animal {
makeSound() {
console.log('Bark')
}
}
class Cat extends Animal {
makeSound() {
console.log('Meow')
}
}
class Bird extends Animal {
makeSound() {
console.log('Chirp')
}
}
const dog = new Dog()
const cat = new Cat()
const bird = new Bird()
dog.makeSound() // Outputs "Bark"
cat.makeSound() // Outputs "Meow"
bird.makeSound() // Outputs "Chirp"
class Animal:
def make_sound(self):
raise NotImplementedError("This method should be overridden")
class Dog(Animal):
def make_sound(self):
print("Bark")
class Cat(Animal):
def make_sound(self):
print("Meow")
class Bird(Animal):
def make_sound(self):
print("Chirp")
dog = Dog()
cat = Cat()
bird = Bird()
dog.make_sound() # Outputs "Bark"
cat.make_sound() # Outputs "Meow"
bird.make_sound() # Outputs "Chirp"
Sample Projects
- Project 1: E-commerce Payment System:
- Description: Design a payment processing system with a base class
PaymentMethod
and subclassesCreditCardPayment
,PayPalPayment
, andBitcoinPayment
. Each subclass should implement a methodprocessPayment
that adheres to LSP. - Example:
- Description: Design a payment processing system with a base class
- Java
- JavaScript
- Python
abstract class PaymentMethod {
public abstract void processPayment(double amount);
}
class CreditCardPayment extends PaymentMethod {
@Override
public void processPayment(double amount) {
System.out.println("Processing credit card payment of " + amount);
}
}
class PayPalPayment extends PaymentMethod {
@Override
public void processPayment(double amount) {
System.out.println("Processing PayPal payment of " + amount);
}
}
class BitcoinPayment extends PaymentMethod {
@Override
public void processPayment(double amount) {
System.out.println("Processing Bitcoin payment of " + amount);
}
}
public class Main {
public static void main(String[] args) {
PaymentMethod payment = new CreditCardPayment();
payment.processPayment(100.0);
payment = new PayPalPayment();
payment.processPayment(200.0);
payment = new BitcoinPayment();
payment.processPayment(300.0);
}
}
class PaymentMethod {
processPayment(amount) {
throw new Error('This method should be overridden')
}
}
class CreditCardPayment extends PaymentMethod {
processPayment(amount) {
console.log(`Processing credit card payment of ${amount}`)
}
}
class PayPalPayment extends PaymentMethod {
processPayment(amount) {
console.log(`Processing PayPal payment of ${amount}`)
}
}
class BitcoinPayment extends PaymentMethod {
processPayment(amount) {
console.log(`Processing Bitcoin payment of ${amount}`)
}
}
let payment = new CreditCardPayment()
payment.processPayment(100.0)
payment = new PayPalPayment()
payment.processPayment(200.0)
payment = new BitcoinPayment()
payment.processPayment(300.0)
class PaymentMethod:
def process_payment(self, amount):
raise NotImplementedError("This method should be overridden")
class CreditCardPayment(PaymentMethod):
def process_payment(self, amount):
print(f"Processing credit card payment of {amount}")
class PayPalPayment(PaymentMethod):
def process_payment(self, amount):
print(f"Processing PayPal payment of {amount}")
class BitcoinPayment(PaymentMethod):
def process_payment(self, amount):
print(f"Processing Bitcoin payment of {amount}")
payment = CreditCardPayment()
payment.process_payment(100.0)
payment = PayPalPayment()
payment.process_payment(200.0)
payment = BitcoinPayment()
payment.process_payment(300.0)
- Project 2: Library Management System:
- Description: Develop a library management system where the base class
Item
is extended by subclassesBook
,Magazine
, andDVD
. Each subclass should implement methods specific to their type while adhering to LSP. - Example:
- Description: Develop a library management system where the base class
- Java
- JavaScript
- Python
abstract class Item {
public abstract void checkOut();
public abstract void returnItem();
}
class Book extends Item {
@Override
public void checkOut() {
System.out.println("Checking out a book");
}
@Override
public void returnItem() {
System.out.println("Returning a book");
}
}
class Magazine extends Item {
@Override
public void checkOut() {
System.out.println("Checking out a magazine");
}
@Override
public void returnItem() {
System.out.println("Returning a magazine");
}
}
class DVD extends Item {
@Override
public void checkOut() {
System.out.println("Checking out a DVD");
}
@Override
public void returnItem() {
System.out.println("Returning a DVD");
}
}
public class Main {
public static void main(String[] args) {
Item item = new Book();
item.checkOut();
item.returnItem();
item = new Magazine();
item.checkOut();
item.returnItem();
item = new DVD();
item.checkOut();
item.returnItem();
}
}
class Item {
checkOut() {
throw new Error('This method should be overridden')
}
returnItem() {
throw new Error('This method should be overridden')
}
}
class Book extends Item {
checkOut() {
console.log('Checking out a book')
}
returnItem() {
console.log('Returning a book')
}
}
class Magazine extends Item {
checkOut() {
console.log('Checking out a magazine')
}
returnItem() {
console.log('Returning a magazine')
}
}
class DVD extends Item {
checkOut() {
console.log('Checking out a DVD')
}
returnItem() {
console.log('Returning a DVD')
}
}
let item = new Book()
item.checkOut()
item.returnItem()
item = new Magazine()
item.checkOut()
item.returnItem()
item = new DVD()
item.checkOut()
item.returnItem()
class Item:
def check_out(self):
raise NotImplementedError("This method should be overridden")
def return_item(self):
raise NotImplementedError("This method should be overridden")
class Book(Item):
def check_out(self):
print("Checking out a book")
def return_item(self):
print("Returning a book")
class Magazine(Item):
def check_out(self):
print("Checking out a magazine")
def return_item(self):
print("Returning a magazine")
class DVD(Item):
def check_out(self):
print("Checking out a DVD")
def return_item(self):
print("Returning a DVD")
item = Book()
item.check_out()
item.return_item()
item = Magazine()
item.check_out()
item.return_item()
item = DVD()
item.check_out()
item.return_item()
Quizzes
- Quiz 1: Identifying LSP Violations:
- Question: Given the following class hierarchy, identify the LSP violation and suggest a way to fix it.
- Java
- JavaScript
- Python
class Vehicle {
public void startEngine() {
System.out.println("Starting engine");
}
}
class Bicycle extends Vehicle {
@Override
public void startEngine() {
throw new UnsupportedOperationException("Bicycles don't have engines");
}
}
public class Main {
public static void main(String[] args) {
Vehicle vehicle = new Bicycle();
vehicle.startEngine(); // Throws UnsupportedOperationException
}
}
class Vehicle {
startEngine() {
console.log('Starting engine')
}
}
class Bicycle extends Vehicle {
startEngine() {
throw new Error("Bicycles don't have engines")
}
}
const vehicle = new Bicycle()
vehicle.startEngine() // Throws Error: Bicycles don't have engines
class Vehicle:
def start_engine(self):
print("Starting engine")
class Bicycle(Vehicle):
def start_engine(self):
raise NotImplementedError("Bicycles don't have engines")
vehicle = Bicycle()
vehicle.start_engine() # Raises NotImplementedError: Bicycles don't have engines
- Answer:
- Java
- JavaScript
- Python
abstract class Vehicle {
public abstract void move();
}
class MotorVehicle extends Vehicle {
@Override
public void move() {
startEngine();
}
public void startEngine() {
System.out.println("Starting engine");
}
}
class Bicycle extends Vehicle {
@Override
public void move() {
pedal();
}
public void pedal() {
System.out.println("Pedaling");
}
}
public class Main {
public static void main(String[] args) {
Vehicle vehicle = new MotorVehicle();
vehicle.move(); // Outputs "Starting engine"
vehicle = new Bicycle();
vehicle.move(); // Outputs "Pedaling"
}
}
class Vehicle {
move() {
throw new Error('This method should be overridden')
}
}
class MotorVehicle extends Vehicle {
move() {
this.startEngine()
}
startEngine() {
console.log('Starting engine')
}
}
class Bicycle extends Vehicle {
move() {
this.pedal()
}
pedal() {
console.log('Pedaling')
}
}
let vehicle = new MotorVehicle()
vehicle.move() // Outputs "Starting engine"
vehicle = new Bicycle()
vehicle.move() // Outputs "Pedaling"
class Vehicle:
def move(self):
raise NotImplementedError("This method should be overridden")
class MotorVehicle(Vehicle):
def move(self):
self.start_engine()
def start_engine(self):
print("Starting engine")
class Bicycle(Vehicle):
def move(self):
self.pedal()
def pedal(self):
print("Pedaling")
vehicle = MotorVehicle()
vehicle.move() # Outputs "Starting engine"
vehicle = Bicycle()
vehicle.move() # Outputs "Pedaling"
- Quiz 2: LSP and OCP:
- Question: How does adhering to LSP support the Open/Closed Principle (OCP)? Provide examples.
- Answer:
- Explanation: Adhering to LSP supports OCP by ensuring that new subclasses can be added without modifying existing code. This extensibility is a core aspect of OCP, which states that software entities should be open for extension but closed for modification.
- Example: In a payment processing system, adding a new
BitcoinPayment
class should not require changes to the existingPaymentProcessor
class if LSP is followed. The new class can be used interchangeably with existing payment methods, supporting OCP.
Multiple-Choice Questions (MCQs)
MCQ 1: What does the Liskov Substitution Principle state?
- A) Subclasses should only inherit methods from their base classes
- B) Objects of a superclass should be replaceable with objects of a subclass without altering the program's correctness
- C) Classes should be open for extension but closed for modification
- D) No client should be forced to depend on methods it does not use
Answer: B) Objects of a superclass should be replaceable with objects of a subclass without altering the program's correctness
MCQ 2: Which of the following is a violation of LSP?
- A) A subclass that adds new methods to the base class
- B) A subclass that overrides a base class method to throw an exception not thrown by the base class
- C) A subclass that implements an interface
- D) A subclass that extends the functionality of the base class
Answer: B) A subclass that overrides a base class method to throw an exception not thrown by the base class
MCQ 3: How can unit testing help verify adherence to LSP?
- A) By testing only the base class methods
- B) By ensuring that subclass methods are never called
- C) By testing both base class and subclass methods to ensure consistent behavior
- D) By avoiding tests that involve inheritance
Answer: C) By testing both base class and subclass methods to ensure consistent behavior
By working through these extensive practice problems, quizzes, and MCQs, you'll gain hands-on experience with the Liskov Substitution Principle, helping you to apply it effectively in your projects. These exercises will deepen your understanding and improve your ability to create maintainable, scalable, and reliable code.