When should I use 'by' delegation instead of manual method forwarding?

← Code Quality · Ref: Q859

When a class manually delegates most trait methods to a field, EK9 detects this pattern and requires using the 'by' keyword instead (E11023). The 'by' keyword eliminates boilerplate delegation code.

THE PROBLEM

Manually overriding 5+ trait methods just to forward calls to a field is tedious and error-prone:

  override methodA() -> delegate.methodA()
  override methodB() -> delegate.methodB()
  override methodC() -> delegate.methodC()

This is boilerplate that the compiler can generate automatically.

THE FIX

Use 'with trait of X by field' to auto-delegate all unoverridden methods:

  MyClass with trait of Formatter by delegate
    delegate as Formatter: ConcreteFormatter()

Now only override the methods you need to customize.

BENEFITS

1. Eliminates N lines of forwarding boilerplate
2. New trait methods are automatically delegated
3. Only customized methods need override
4. Clear intent: delegation is visible in the class declaration

See Q210 for trait delegation basics. See Q264 for adapter pattern. See Q266 for cross-cutting concerns via delegation.

Example

defines module qa.quality.use.by.delegation

  defines trait

    <?-
      Formatter trait with multiple methods.
      Used to demonstrate delegation with 'by'.
    -?>
    Formatter
      formatHeading() as abstract
        -> heading as String
        <- rtn as String?

      formatParagraph() as abstract
        -> paragraph as String
        <- rtn as String?

      formatFooter() as abstract
        <- rtn as String?

  defines class

    <?-
      Concrete implementation of Formatter.
      Provides default formatting behavior.
    -?>
    PlainFormatter with trait of Formatter
      override formatHeading()
        -> heading as String
        <- rtn as String: `=== ${heading} ===`

      override formatParagraph()
        -> paragraph as String
        <- rtn as String: "  " + paragraph

      override formatFooter()
        <- rtn as String: "---end---"

      default operator ?

    <?-
      Correct: uses 'by' delegation to auto-delegate Formatter methods.
      Only overrides formatHeading to add custom behavior.
      formatParagraph and formatFooter are auto-delegated.
    -?>
    FancyFormatter with trait of Formatter by delegate
      delegate as Formatter: PlainFormatter()

      FancyFormatter()
        -> formatter as Formatter
        this.delegate: formatter

      override formatHeading()
        -> heading as String
        <- rtn as String: `*** ${heading} ***`

      default operator ?

  defines program

    DelegationDemo()
      stdout <- Stdout()

      plain <- PlainFormatter()
      fancy <- FancyFormatter(plain)

      //formatHeading is overridden in FancyFormatter
      stdout.println(fancy.formatHeading("Title"))

      //formatParagraph is auto-delegated to PlainFormatter
      stdout.println(fancy.formatParagraph("Some content here"))

      //formatFooter is auto-delegated to PlainFormatter
      stdout.println(fancy.formatFooter())
Other ways to ask this
  • What is E11023 MISSING_BY_DELEGATION in EK9?
  • Why does EK9 require 'by' delegation for trait forwarding?
  • How do I eliminate boilerplate delegation code?

Coming from another language?

Java: no built-in delegation, manual forwarding or Lombok @Delegate. Kotlin: 'by' delegation on interfaces, same concept. Rust: no delegation, manual impl forwarding. Go: embedded structs provide implicit forwarding. Python: __getattr__ for dynamic delegation. EK9: 'with trait of X by field' with E11023 enforcement when manual delegation detected.

Keywords: override, forwarding, quality, trait, clean-code, by, composition, boilerplate, E11023, delegation