# When Inheritance Goes Wrong — Object-Oriented Programming (OOP)

Source: https://www.skillbyai.com/en/oop/i-pitfalls

> Recognise fragile base classes, deep hierarchies, the diamond problem and LSP violations.

## Powerful, and easy to misuse

Inheritance couples a subclass tightly to its parent's implementation. The **fragile base class** problem: a change to a superclass, even an internal one, can break subclasses that relied on how it worked. A classic example is extending `HashSet` to count additions by overriding both `add` and `addAll`, then double-counting because `addAll` calls `add` internally. **Deep hierarchies** (five or six levels) become hard to understand, because behaviour is scattered across many classes. The **diamond problem** arises with multiple inheritance: if B and C both inherit from A and override a method, and D inherits from both, which version does D get? C++ addresses it with **virtual inheritance**, Python with a defined **method resolution order (MRO)** using C3 linearisation, and Java avoids it for classes but must resolve conflicting **default methods** in interfaces, where the class must override and choose explicitly. Most importantly, a subclass must not break the parent's **contract**, the Liskov substitution principle covered later. These problems are why designers say "favour composition over inheritance".

## The diamond with Java default methods

Two interfaces provide the same default method; the class must resolve the conflict.

```java
interface Flyer   { default String move() { return "fly"; } }
interface Swimmer { default String move() { return "swim"; } }

class Duck implements Flyer, Swimmer {
    @Override
    public String move() {                       // required: otherwise compile error
        return Flyer.super.move() + " and " + Swimmer.super.move();
    }
}

// Python resolves with the method resolution order (MRO)
// class A: def hi(self): return "A"
// class B(A): def hi(self): return "B"
// class C(A): def hi(self): return "C"
// class D(B, C): pass
// D().hi() -> "B"   (D.__mro__: D, B, C, A, object)
```

## Design for inheritance or forbid it

Joshua Bloch's advice in Effective Java: either document precisely how a class may be extended (which methods call which) or make it `final`. Accidental extensibility is a source of fragile subclasses.

**Quiz:** How does Java handle a class that implements two interfaces with conflicting default methods?

- [ ] It picks one at random
- [ ] It is a runtime error
- [x] The class must override the method and resolve the conflict explicitly
- [ ] The first interface listed always wins

*Answer:* The class must override the method and resolve the conflict explicitly. The compiler requires the class to override and may call InterfaceName.super.method().
