How do I implement the adapter pattern in EK9?

← Design Patterns and Idioms · Ref: Q264

EK9 implements the adapter pattern using composition with trait implementation. The adapter class wraps the adaptee as a field and translates method calls from the new interface to the old one.

ADAPTER STRUCTURE

The pattern has three parts:

  1. Adaptee: the existing class with its own interface
  2. Target trait: the new interface clients expect
  3. Adapter: wraps the adaptee and implements the target trait

COMPOSITION WRAPPING

The adapter holds the adaptee as a private field:

  PrinterAdapter with trait of ModernOutput
    printer as OldPrinter

The adapter translates ModernOutput.render() into OldPrinter.printText().

METHOD TRANSLATION

The adapter overrides trait methods and delegates to the adaptee:

  override render()
    -> content as String
    <- rtn as String: printer.printText(content)

Clients call render() but the work is done by printText().

TRAIT DELEGATION SHORTCUT

When the adaptee already partially matches the target trait, use trait delegation with by to auto-forward compatible methods and only override the incompatible ones.

WHEN TO USE ADAPTER

Use when integrating code with different interfaces. The adapter translates without modifying either the adaptee or the client code.

See Q109 for composition patterns. See Q210 for trait delegation with by. See Q212 for composition over inheritance.

Example

defines module qa.patterns.adapter

  defines class

    // The existing class with its own interface
    OldPrinter

      printText()
        -> text as String
        <- rtn as String: "[OLD] " + text

      default operator ?

  defines trait

    // The new interface clients expect
    ModernOutput
      render() as abstract
        -> content as String
        <- rtn as String?

  defines class

    // Adapter: wraps OldPrinter, implements ModernOutput
    PrinterAdapter with trait of ModernOutput
      printer as OldPrinter: OldPrinter()

      PrinterAdapter()
        -> p as OldPrinter
        this.printer: p

      override render()
        -> content as String
        <- rtn as String: printer.printText(content)

      default operator ?

  defines program

    AdapterPatternDemo()
      stdout <- Stdout()

      // === OLD INTERFACE ===

      oldPrinter <- OldPrinter()
      stdout.println(oldPrinter.printText("Direct call"))

      // === ADAPTED INTERFACE ===

      adapter <- PrinterAdapter(oldPrinter)
      stdout.println(adapter.render("Through adapter"))

      // === CLIENT USES MODERN INTERFACE ===
      // The client only knows about ModernOutput

      outputs <- List() of ModernOutput
      outputs += PrinterAdapter(OldPrinter())
      outputs += PrinterAdapter(OldPrinter())

      for output in outputs
        stdout.println(output.render("Polymorphic call"))

Common mistakes

E05120 — When implementing an abstract trait method in a class, the 'override' keyword is required. PrinterAdapter must use 'override render()' to implement ModernOutput's abstract render method. See ek9 -h E05120 for details.

Incorrect:

render()

Correct:

override render()

E05030 — OldPrinter is closed by default and cannot be extended. EK9 types are closed unless declared 'as open'. Use composition (wrapping as a field) and trait implementation instead of inheritance. See ek9 -h E05030 for details.

Incorrect:

PrinterAdapter extends OldPrinter

Correct:

PrinterAdapter with trait of ModernOutput
Other ways to ask this
  • How do I adapt one interface to another in EK9?
  • How do I wrap an existing class with a new interface?
  • How do I translate between incompatible interfaces in EK9?

Coming from another language?

Java: Adapter pattern with class wrapping or interface delegation, anonymous classes. Python: duck typing often avoids adapter, __getattr__ for delegation. Rust: newtype pattern (struct Wrapper(Inner)), impl Trait for Wrapper. Go: struct embedding, interface satisfaction. Kotlin: class delegation with by keyword. EK9: composition wrapping with trait implementation, trait delegation with by for partial adaptation.

Keywords: wrap, translate, design, compose, compatibility, adapter, delegate, idiom, convert, interface, bridge, pattern