How do I create a singleton in EK9?
← Classes and OOP · Ref: Q117
EK9 has no traditional singleton pattern. There is no private constructor trick, no static getInstance() method, and no object keyword. The ONLY way to have a single shared instance is through Dependency Injection and Inversion of Control using the component and application registration system.
WHY NO TRADITIONAL SINGLETONS
Traditional singletons create hidden global state, tight coupling, and testing nightmares. EK9 enforces that shared instances are explicitly registered and injected, making dependencies visible and testable.
HOW SINGLE INSTANCES WORK
Register a component in an application definition. All injection points receive the same instance:
defines component AppConfig as abstract setting() as abstract <- rtn as String? default operator ? defines application MyApp register ProductionConfig() as AppConfig
The '!' suffix on a type declaration triggers injection: config as AppConfig!
LIFECYCLE
The registered component instance is created once during application startup. Every injection point across the application receives the same instance.
THREAD SAFETY
Since components are shared, use immutable state or MutexLock for mutable data. Pure methods ensure thread-safe reads.
See Q111 for components and DI. See Q114 for aspects. See Q119 for constructs overview. See Q234 for component lifecycle details.
See Q330 for why EK9 uses singleton-only DI scope.
Example
defines module qa.oop.singleton defines component AppConfig as abstract setting() as abstract <- rtn as String? default operator ? ProductionConfig extends AppConfig override setting() <- rtn as String: "production-mode" default operator ? defines application SingletonApp register ProductionConfig() as AppConfig defines program SingletonDemo() with application of SingletonApp stdout <- Stdout() // === SINGLETON via component injection === config1 as AppConfig! config2 as AppConfig! // Both reference the same registered instance stdout.println(`Config1: ${config1.setting()}`) stdout.println(`Config2: ${config2.setting()}`) // === Same value from both injection points === stdout.println(`Same: ${config1.setting() == config2.setting()}`)
Common mistakes
E05120 — When implementing an abstract method from the parent component AppConfig, the 'override' keyword is mandatory. ProductionConfig must use 'override setting()'. See ek9 -h E05120 for details.
Incorrect:
setting()
Correct:
override setting()
E50040 — Without 'as abstract', AppConfig has an abstract method 'setting()' but the component itself is not marked abstract. The compiler requires types with abstract methods to be declared 'as abstract'. See ek9 -h E50040 for details.
Incorrect:
AppConfig
Correct:
AppConfig as abstract
E50060 — EK9 does not use Java-style getter naming. The method is 'setting()' not 'getSetting()'. See ek9 -h E50060 for details.
Incorrect:
config1.getSetting()
Correct:
config1.setting()
E50060 — EK9 does not have a toString() method. Use string interpolation or the $ operator instead. See ek9 -h E50060 for details.
Incorrect:
stdout.println(config1.toString())
Correct:
stdout.println(`Config1: ${config1.setting()}`)
Other ways to ask this
- Does EK9 support the singleton pattern?
- How do I ensure only one instance of a class exists?
- What is the EK9 approach to singletons?
Coming from another language?
Java: private constructor + static getInstance(), or Spring @Singleton/@Scope. Python: module-level instances, or __new__ override. Rust: lazy_static! or once_cell for global state. Go: sync.Once for lazy initialisation, package-level variables. Kotlin: 'object' keyword creates a singleton. EK9: component registration provides singleton lifecycle, injection with '!' suffix, no explicit singleton pattern needed, thread safety through pure methods or MutexLock.
Keywords: thread, lifecycle, inject, register, define, singleton, shared, object-oriented, safety, pattern, instance, component