Skip to main content

Forced Compatibility

Description​

What does it look like?​

  • New code directly calls legacy (outdated) interfaces
  • The caller manually adapts to differences in API specifications, cluttering the logic
  • Despite mismatched formats or behavior, the integration is forced for the sake of functionality

Why is it a problem?​

  • The calling side takes on the responsibility of adapting/transforming the data, blurring design boundaries
  • Any change in the API or specification ripples across many parts of the codebase
  • Makes testing or replacing the dependency difficult

Bad Example of the Anti-pattern​

// レガシーAPIοΌˆζ—§δ»•ζ§˜οΌ‰
class LegacyPrinter {
printText(text: string) {
console.log(`*** ${text} ***`);
}
}

// 新しいコード
class ReportGenerator {
printSummary(text: string) {
const printer = new LegacyPrinter(); // ← η›΄ζŽ₯δΎε­˜οΌˆγ‚€γƒ³γ‚ΏγƒΌγƒ•γ‚§γƒΌγ‚ΉδΈδΈ€θ‡΄οΌ‰
// η„‘η†γ‚„γ‚Šζ—§APIγ«εˆγ‚γ›γ‚‹
const legacyFormat = text.toUpperCase();
printer.printText(legacyFormat); // ← ハードコーディング
}
}

Issues:​

  • ReportGenerator is tightly coupled to LegacyPrinter’s method names and formatting
  • Interface mismatch is not abstracted; legacy and modern design philosophies are mixed
  • Logic like converting text to uppercase is embedded in ReportGenerator, violating the SRP
  • Cannot easily switch to a new printer class or external printing API
  • Cannot replace with a mock printer during testing
  • As usage of LegacyPrinter increases, similar transformation logic is duplicated throughout the codebase
  • Any change to the legacy interface impacts all usage sites

Refactoring by Pattern​

Design patterns that can address this​

PatternOverviewMain Refactoring Approach
AdapterActs as a β€œbridge” to match interface differencesAdds a translation layer between old and new APIs
FacadeHides subsystem complexity and provides a clean entry pointUse when you want to simplify usage
ProxyControls access to objectsUseful for access control, caching, or lazy calls