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