How do I implement the factory pattern in EK9?

← Design Patterns and Idioms · Ref: Q263

EK9 implements the factory pattern using functions that return trait implementations. The factory uses switch/case to select the appropriate concrete class based on input parameters.

TRAIT AS PRODUCT INTERFACE

Define a trait as the product contract:

  Logger
    log() as abstract
      -> msg as String
      <- rtn as String?

All factory products implement this trait.

CONCRETE IMPLEMENTATIONS

Create classes that implement the trait:

  ConsoleLogger with trait of Logger
    override log()
      -> msg as String
      <- rtn as String: "[CONSOLE] " + msg
  PrefixLogger with trait of Logger
    override log()
      -> msg as String
      <- rtn as String: "[PREFIX] " + msg

FACTORY FUNCTION WITH SWITCH

The factory selects the implementation:

  createLogger()
    -> logType as String
    <- rtn as Logger?
    switch logType
      case "console"
        rtn: ConsoleLogger()
      default
        rtn: ConsoleLogger()

DYNAMIC CLASS FACTORY

For lightweight one-off implementations, create dynamic classes inside the factory:

  customLogger <- () with trait of Logger as class
    override log()
      -> msg as String
      <- rtn as String: "[CUSTOM] " + msg

WHEN TO USE FACTORY

Use factories when the caller should not know which concrete class is created. The factory encapsulates the creation logic and returns the trait type.

See Q103 for abstract classes as alternatives to traits. See Q115 for dynamic classes in factories. See Q211 for dispatcher as an alternative to factory switch logic.

Example

defines module qa.patterns.factory

  defines trait

    Logger
      log() as abstract
        -> msg as String
        <- rtn as String?

  defines class

    ConsoleLogger with trait of Logger
      override log()
        -> msg as String
        <- rtn as String: "[CONSOLE] " + msg

      default operator ?

    PrefixLogger with trait of Logger
      logPrefix as String: "[INFO]"

      PrefixLogger()
        -> p as String
        this.logPrefix: p

      override log()
        -> msg as String
        <- rtn as String: `${logPrefix} ${msg}`

      default operator ?

  defines function

    createLogger()
      -> logType as String
      <- rtn as Logger?
      switch logType
        case "console"
          rtn: ConsoleLogger()
        case "prefix"
          rtn: PrefixLogger("[WARN]")
        default
          rtn: ConsoleLogger()

  defines program

    FactoryPatternDemo()
      stdout <- Stdout()

      // === FACTORY FUNCTION ===

      console <- createLogger("console")
      stdout.println(console.log("Hello from factory"))

      prefixed <- createLogger("prefix")
      stdout.println(prefixed.log("Warning message"))

      fallback <- createLogger("unknown")
      stdout.println(fallback.log("Fallback to default"))

      // === DYNAMIC CLASS AS PRODUCT ===

      custom <- () with trait of Logger as class
        override log()
          -> msg as String
          <- rtn as String: "[CUSTOM] " + msg

      stdout.println(custom.log("Dynamic logger"))

Common mistakes

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

Incorrect:

log()

Correct:

override log()
Other ways to ask this
  • How do I create objects via a factory function in EK9?
  • How do I return different implementations from a function?
  • How do I use switch to select implementations in EK9?

Coming from another language?

Java: Factory Method pattern with abstract creator class, Abstract Factory for families, static factory methods. Python: factory functions, __init__ with type dispatch. Rust: associated functions (Type::new()), builder pattern. Go: constructor functions NewType(), no inheritance. Kotlin: companion object factory methods, sealed classes with when. EK9: factory functions returning trait types, switch/case for selection, dynamic classes for inline implementations.

Keywords: builder, construct, design, idiom, implementation, factory, switch, pattern, trait, create, concrete, select