# Common OOP Mistakes — Object-Oriented Programming (OOP)

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

> Recognise and avoid frequent object-oriented design mistakes.

## Ways object-oriented code goes wrong

**God object**: one huge class (`Manager`, `Utils`, `AppService`) that knows and does everything, violating SRP. **Anaemic domain model**: classes with only fields, getters and setters, while all behaviour lives in separate service classes, losing encapsulation. **Getter/setter for every field**: exposes internals and lets callers break invariants. **Inheritance for code reuse**: extending a class just to reuse methods, creating fragile is-a relationships that are not true. **Deep hierarchies** and **premature abstraction**: interfaces, factories and abstract classes added "just in case" before any second implementation exists. **Static everything**: utility classes and static state that hide dependencies and resist testing. **Downcasting and instanceof chains** instead of polymorphism. **Mutable shared state** without clear ownership. **Ignoring equals/hashCode contracts**. The cure is usually the same: give classes clear responsibilities, keep behaviour with the data it protects, prefer composition, introduce abstractions when a real need appears, and keep designs as simple as the problem allows.

## Anaemic model versus rich model

In the rich version, the object protects its own rules.

```java
// anaemic: data bag + logic elsewhere; any code can break the rules
class Course { public int seats; public List<String> enrolled = new ArrayList<>(); }
class EnrolmentService {
    void enrol(Course c, String student) {
        if (c.enrolled.size() < c.seats) c.enrolled.add(student);
    }
}
// elsewhere: course.enrolled.add("x") bypasses the seat limit

// rich: the rule lives with the data
final class RichCourse {
    private final int seats;
    private final Set<String> enrolled = new HashSet<>();
    RichCourse(int seats) { this.seats = seats; }

    boolean enrol(String student) {
        if (enrolled.size() >= seats || enrolled.contains(student)) return false;
        return enrolled.add(student);
    }
    Set<String> enrolled() { return Set.copyOf(enrolled); }   // read-only view
}
```

## YAGNI applies to abstractions too

"You aren't gonna need it": add an interface when you have, or clearly expect, a second implementation or need a test seam. Abstractions have a cost in indirection and reading time.

**Quiz:** What is an anaemic domain model?

- [x] Classes holding data with getters and setters while business rules live elsewhere, so objects cannot protect their invariants
- [ ] A model with too many methods
- [ ] A database with no tables
- [ ] A model that uses only static classes for performance

*Answer:* Classes holding data with getters and setters while business rules live elsewhere, so objects cannot protect their invariants. Separating all behaviour from data loses the main benefit of encapsulation.
