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