CodePark/ questions / Composition & mixins
START PRACTICINGPRACTICE
Backend question · Object-oriented programming › Composition & mixins

Every service in the codebase extends this BaseService. What problems does it cause, and how would you refactor it?

abstract class BaseService {
  protected logger = new Logger();
  protected cache = new RedisCache();
  protected db = getDb();
  protected async cached<T>(key: string, fn: () => Promise<T>) { /* ... */ }
  protected paginate(q: Query, page: number) { /* ... */ }
}
class InvoiceService extends BaseService { /* uses only db */ }

This is a "god base class". It uses inheritance only to share helpers, and it causes several problems:

  • Every service gets everything. InvoiceService needs only db, but it still connects to Redis.
  • Hidden dependencies. The constructor shows nothing. You must read the base class to know what a service uses.
  • Hard to test. new Logger() and getDb() run inside the base, so you can't pass in fakes.
  • Fragile. One change to BaseService touches every service.

Refactor with composition:

  • Turn each helper into its own small class: Logger, Cache, Paginator.
  • Inject only what each service needs: constructor(private db: Db, private logger: Logger) {}.
  • Turn pure helpers like paginate into plain exported functions.
  • Move one service at a time. Delete BaseService when nothing extends it.

After this, each constructor is an honest list of dependencies. Tests can pass small fakes.

  • A base class used only to share helpers gives every subclass dependencies it does not need.
  • Dependencies hidden in a base class are not visible in the constructor.
  • Helpers created inside the base class cannot be replaced in tests.
  • Extract each helper and inject only what each service uses.
  • Refactor one service at a time until the base class can be deleted.