Why can't I mix 'by' delegation with many service fields?

← Code Quality · Ref: Q860

Using 'by' delegation (class pattern) while having 3+ service fields (component pattern) mixes two architectural roles in one type. The compiler raises E11024 to enforce separation of concerns.

WHY THIS IS A PROBLEM

Delegation classes wrap and decorate a single concern. Components coordinate multiple services. Combining both patterns creates a type that is half-decorator and half-coordinator, making it hard to test, reason about, and maintain.

THE FIX

Split into two types:
1. A focused delegation class that wraps one trait with 'by'
2. A component that coordinates multiple services

THIS EXAMPLE

Shows the correct approach: a delegation class (LoggingReporter) wraps a single trait with 'by' and has at most one or two service fields. The service coordination is kept separate.

See Q856 for class vs component. See Q859 for 'by' delegation. See Q310 for code quality overview.

Example

defines module qa.quality.no.hybrid.class.component

  defines trait

    <?-
      Reporter trait: a single concern for reporting.
    -?>
    Reporter
      report() as abstract
        -> topic as String
        <- rtn as String?

  defines class

    <?-
      Concrete reporter implementation.
    -?>
    PlainReporter with trait of Reporter
      override report()
        -> topic as String
        <- rtn as String: "Report on " + topic

      default operator ?

    <?-
      Correct: focused delegation class.
      Wraps Reporter with 'by' and has minimal service fields.
      No hybrid mixing of delegation and service coordination.
    -?>
    LoggingReporter with trait of Reporter by delegate
      delegate as Reporter: PlainReporter()

      LoggingReporter()
        -> reporter as Reporter
        this.delegate: reporter

      override report()
        -> topic as String
        <- rtn as String: "[LOG] " + delegate.report(topic)

      default operator ?

  defines program

    NoHybridDemo()
      stdout <- Stdout()

      plain <- PlainReporter()
      logging <- LoggingReporter(plain)

      //Delegation class focuses on one concern: adding logging
      stdout.println(logging.report("quarterly-sales"))
      stdout.println(logging.report("annual-review"))

Common mistakes

E11024 — Combining 'by' delegation with three or more additional service fields mixes decorator and coordinator roles and triggers E11024 - split into a focused delegation class and a separate component. See ek9 -h E11024 for details.

Incorrect:

      delegate as Reporter: PlainReporter()
      svcA as Reporter: PlainReporter()
      svcB as Reporter: PlainReporter()
      svcC as Reporter: PlainReporter()
      auditLabel as String: "audit"

      LoggingReporter()

Correct:

      delegate as Reporter: PlainReporter()

      LoggingReporter()
Other ways to ask this
  • What is E11024 HYBRID_CLASS_COMPONENT in EK9?
  • Why does EK9 flag my class as a hybrid class-component?
  • How do I separate delegation from service coordination?

Coming from another language?

Java: no detection of mixed delegation and service patterns. Spring allows mixing @Delegate with @Autowired freely. Python: no detection. Kotlin: 'by' delegation has no service field checks. Rust: no class/component distinction. EK9: E11024 compile-time enforcement separating delegation from service coordination.

Keywords: delegation, component, service, architecture, class, clean-code, separation, hybrid, by, quality, E11024