Skip to main content

Tight Coupling

Descriptionโ€‹

What does it look like?โ€‹

  • A class (e.g., OrderService) directly instantiates or calls another class (e.g., EmailService, InventoryService)
  • Due to strong dependencies, any change to a dependency immediately affects the caller
  • Difficult to substitute mocks or stubs during testing
  • Readability, flexibility, and testability suffer

Why is it a problem?โ€‹

  • Every addition or modification requires changes in the original class, resulting in high maintenance cost
  • Leads to a state where โ€œit works, so donโ€™t touch it,โ€ accumulating technical debt
  • The class ends up with multiple responsibilities, resulting in poor separation of concerns

Bad Example of the Anti-patternโ€‹

class Mailer {
send(email: string, content: string) {
console.log(`้€ไฟกใ—ใพใ—ใŸ: ${email} โ†’ ${content}`);
}
}

class UserService {
private mailer: Mailer;

constructor() {
this.mailer = new Mailer(); // โ† ใ‚ฏใƒฉใ‚นๅ†…ใง็›ดๆŽฅ็”Ÿๆˆ๏ผˆๅฏ†็ตๅˆ๏ผ‰
}

notifyUser(email: string) {
const content = "ใ‚ˆใ†ใ“ใ๏ผ";
this.mailer.send(email, content); // โ† Mailer ใฎๅญ˜ๅœจใ‚’็Ÿฅใ‚Šใ™ใŽใฆใ„ใ‚‹
}
}

Issues:โ€‹

  • UserService is tightly coupled to Mailer, and locked into its specific implementation
  • Difficult to switch to another mail system (e.g., external API, mock service)
  • If requirements change (e.g., logging mailer, switching to SMS), changes must be made to UserService
  • Even if you want to share the Mailer logic, each instance is created separately, making it hard to centralize configuration

Refactoring by Patternโ€‹

Design patterns that can address thisโ€‹

PatternOverviewMain Refactoring Approach
ObserverDelegate processing via event notificationUse external registration for loose coupling
MediatorA central coordinator mediates communicationReduce many-to-many dependencies to 1-to-1
Dependency Injection (DI)Inject dependencies from outside for easy replacementImproves testability and extensibility