What is the hybrid class-component anti-pattern in EK9?

← Code Quality · Ref: Q959

When a class uses 'by' delegation AND has 3 or more additional service fields (trait/abstract typed), it is mixing class and component responsibilities. E11024 triggers.

WHY THIS MATTERS

A class using 'by' delegation is extending behavior through composition (a class pattern). Having 3+ service fields means it is also coordinating multiple services (a component pattern). Mixing these patterns creates maintenance problems.

THRESHOLD

- Must have at least 1 'by' delegation
- Must have 3 or more additional service-type fields (not counting 'by' delegate fields)

THIS EXAMPLE

DocumentManager uses 'by' delegation for Storable trait and has 2 additional service fields (formatter and encoder). With only 2 service fields, this is under the threshold of 3.

The typicalError mutation adds a third service field, hitting the threshold and triggering E11024.

See Q314 for cohesion and coupling. See Q111 for component basics.

Example

defines module qa.quality.excessiveservicefields

  defines trait

    Storable
      store() abstract
      retrieve() abstract

    Formatter
      formatContent() abstract

    Encoder
      encodeContent() abstract

    Compressor
      compress() abstract

  defines class

    StorableImpl with trait of Storable
      override store()
        require true
      override retrieve()
        require true

    PlainFormatter with trait of Formatter
      override formatContent()
        require true

    SimpleEncoder with trait of Encoder
      override encodeContent()
        require true

    BasicCompressor with trait of Compressor
      override compress()
        require true

  defines class

    <?-
      DocumentManager uses 'by' delegation for Storable
      and has 2 additional service fields (under threshold of 3).
      Adding a 3rd service field would trigger E11024.
    -?>
    DocumentManager with trait of Storable by persistence
      persistence Storable: StorableImpl()

      formatter Formatter: PlainFormatter()
      encoder Encoder: SimpleEncoder()

      title as String: "untitled"

      processDocument()
        formatter.formatContent()
        encoder.encodeContent()
        persistence.store()

      default operator ?

  defines program

    ExcessiveServiceFieldsDemo()
      stdout <- Stdout()
      stdout.println("DocumentManager has 1 by-delegation and 2 service fields")
      stdout.println("Under the threshold of 3 additional services")

Common mistakes

E11024 — Adding a third service field (compressor) to a class with 'by' delegation triggers E11024 (3 >= threshold of 3). Split into a component for service coordination. See ek9 -h E11024 for details.

Incorrect:

      formatter Formatter: PlainFormatter()
      encoder Encoder: SimpleEncoder()
      compressor Compressor: BasicCompressor()

Correct:

      formatter Formatter: PlainFormatter()
      encoder Encoder: SimpleEncoder()
Other ways to ask this
  • What triggers E11024 HYBRID_CLASS_COMPONENT?
  • Why does EK9 reject classes with delegation and many service fields?
  • How many service fields can a class with 'by' delegation have?

Coming from another language?

Java: no 'by' delegation, no construct distinction. Spring beans mix data and services freely. C#: no built-in delegation detection. Go: no class vs component distinction. EK9: detects hybrid patterns at compile time.

Keywords: service, threshold, field, component, class, quality, by, E11024, hybrid, delegation