Refactor a God class using SOLID
Take a realistic God class that handles HTTP request parsing, database writes, and email sending all in one place. Refactor it into three focused classes connected by dependency injection, applying the Single Responsibility and Dependency Inversion principles explicitly. The interface should not change from the caller's perspective.
Why this matters
God classes are the most common form of technical debt in production codebases. They are hard to test, hard to change, and impossible to reuse. Learning to decompose them using SOLID is not academic; it is the core skill that separates engineers who can work in any codebase from those who can only work in their own.
Before you start
- Comfortable reading and writing Python or TypeScript classes
- Basic understanding of what a class constructor and instance method are
- Some exposure to the idea that functions should do one thing
- No prior knowledge of SOLID required; you will learn it by doing
Step-by-step guide
- 1
Read the God class and list its responsibilities
Write down every distinct thing the class does. Do not look for patterns yet; just list actions. A well-decomposed God class will have 3-5 clearly separable responsibilities. If you find more than 6, it may need two rounds of refactoring.
- 2
Define interfaces for each responsibility
Before writing any classes, write Python Protocols or TypeScript interfaces for each responsibility. An interface for a database writer might have a single method: save(record) -> None. Writing interfaces first forces you to think about contracts before implementation.
- 3
Extract each class
Create one class per responsibility, each implementing its interface. Move the relevant methods from the God class verbatim first; do not improve them yet. Verify each class can be instantiated and called in isolation before connecting them.
- 4
Connect via dependency injection
Rewrite the original class to accept the three new classes in its constructor rather than creating them internally. The caller now controls which implementations are injected. This is what makes the system testable; you can inject a mock database writer without changing the class.
- 5
Write three unit tests
Write one test per extracted class, injecting mocks for any dependencies. If a test requires more than 5 lines of setup, your decomposition may not be clean enough. The tests should be fast, isolated, and read like specifications.