For example:A "Lion" (a derived class of "Mammal") can be substituted for an "Animal" in the program, and it can eat, sleep, and make sounds just like any other animal.
A "Penguin" (a derived class of "Bird") should also be substitutable for an "Animal" and exhibit the same basic behaviors.Key Idea of LSP
The LSP says:
If you have a base class (
Animal), you should be able to replace it with any of its subclasses (Lion,Penguin) without breaking the functionality of your program.
A violation happens when the subclass changes behavior in a way that:
- Introduces unexpected restrictions or exceptions.
- Breaks the assumptions made in the base class (
Animal). - Forces clients (code that uses
Animal) to handle subclasses differently.
LSP Violation 1: Adding Restrictions
Example: Lion changes eat behavior
In the base class (Animal), the eat method is assumed to work for all animals without restrictions.
If the Lion class overrides eat and introduces a restriction (e.g., lions only eat meat), the program might break when a Lion is substituted for an Animal.
class Lion extends Animal {
@Override
public void eat() {
throw new UnsupportedOperationException("Lions can only eat meat!");
}
}
// Test Case
Animal animal = new Lion(); // Substituting Lion for Animal
animal.eat(); // Throws an exception!
Why This Breaks LSP:
- The code expects
eat()to work for allAnimalobjects. - A
Lionviolates this expectation by restricting what it can eat.
LSP Violation 2: Changing Return Type
If the subclass (Lion) changes the return type of a method, it will break polymorphism and substitutability.
Example: Lion's makeSound returns a String instead of void
class Lion extends Animal {
@Override
public String makeSound() { // Invalid override
return "Roar!";
}
}
// Test Case
Animal animal = new Lion(); // Substituting Lion for Animal
animal.makeSound(); // Compilation error! Method signature mismatch.
Why This Breaks LSP:
- The base class defines
makeSound()as returningvoid. - A subclass (
Lion) changes the return type, making it incompatible with the base class.
LSP Violation 3: Changing Preconditions
If a subclass (Penguin) introduces stricter conditions on when a method can be used, it breaks LSP.
Example: Penguin changes sleep behavior
class Penguin extends Animal {
@Override
public void sleep() {
if (!isNightTime()) {
throw new IllegalStateException("Penguins only sleep at night!");
}
super.sleep();
}
private boolean isNightTime() {
return false; // Simulating day time
}
}
// Test Case
Animal animal = new Penguin(); // Substituting Penguin for Animal
animal.sleep(); // Throws IllegalStateException!
Why This Breaks LSP:
- The base class (
Animal) promises that all animals can sleep at any time. - The subclass (
Penguin) introduces a condition that breaks this promise.
LSP Violation 4: Violating Postconditions
Postconditions are guarantees made by the base class after a method is called. If a subclass weakens these guarantees, it violates LSP.
Example: Lion changes makeSound behavior
In the base class, makeSound guarantees that every animal produces a sound. If Lion violates this by failing to make a sound, it breaks LSP.
class Lion extends Animal {
@Override
public void makeSound() {
// Breaks the guarantee that all animals make a sound
throw new UnsupportedOperationException("This lion is silent!");
}
}
// Test Case
Animal animal = new Lion(); // Substituting Lion for Animal
animal.makeSound(); // Throws exception!
Why This Breaks LSP:
- The base class (
Animal) promises that all animals will make a sound. - The subclass (
Lion) breaks this promise, causing unexpected behavior.
Key Points to Remember
- Base Class Assumptions: The base class (
Animal) makes certain promises (e.g.,eat,sleep, andmakeSoundalways work without extra restrictions). - Substituting Subclasses: Subclasses (
Lion,Penguin) should fulfill those promises without adding stricter requirements or removing functionality. - Breaking LSP: If a subclass changes behavior (e.g., adds restrictions, changes return types, or fails to meet expectations), it breaks LSP.
How to Avoid LSP Violations
- Follow the Base Class Contract: Ensure that subclasses respect the behavior defined in the base class.
- Avoid Strengthening Preconditions: Don’t add new requirements (e.g., time of day) in subclass methods.
- Avoid Weakening Postconditions: Ensure that subclasses meet the guarantees promised by the base class.
- Use Composition Over Inheritance: If the subclass behavior is too different, consider using composition instead of inheritance.
No comments:
Post a Comment