Skip to content

Commit 2126aeb

Browse files
CopilotbedaHovorka
andcommitted
docs: Document factory pattern implementation and architecture
Add comprehensive documentation: - FACTORY_PATTERN_IMPLEMENTATION.md - Summary of changes, benefits, and future work - Update CLAUDE.md architecture section with factory pattern details - Update CLAUDE.md DI module organization - Document SimulationProcessFactory design and usage Documentation covers: - Problem solved (dependency inversion violation) - Solution implemented (factory pattern with DI) - Benefits achieved (testability, flexibility, maintainability) - Remaining work (class splitting, static/dynamic properties) - Migration guide for custom factories - Testing strategy Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com>
1 parent 78713f7 commit 2126aeb

2 files changed

Lines changed: 259 additions & 3 deletions

File tree

CLAUDE.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,21 @@ For detailed Docker + Koin integration guide, see:
317317

318318
**Context system:**
319319
- `Context` - Base abstraction for railway network configuration
320-
- `SimulationContext` - Simulation execution context
320+
- `SimulationContext` - Simulation execution context
321+
- `EditingContext` - Editing operations for railway network
322+
- `DefaultContext` - Implementation combining both editing and simulation capabilities
323+
- `SimulationProcessFactory` - Factory interface for creating simulation processes (decouples context from concrete sim/ classes)
324+
- `DefaultSimulationProcessFactory` - Default factory implementation using jDisco-based processes (Generator, InOutWorker)
321325
- `EditingContextFactory` / `SimulationContextFactory` - Factory pattern for context creation
322326
- `XMLContextFactory` - Creates contexts from XML files (defined by `data.xsd` schema)
323327

328+
**Factory Pattern (2026-01-14):**
329+
DefaultContext now uses dependency injection to obtain a `SimulationProcessFactory` rather than directly instantiating simulation classes. This:
330+
- Follows Dependency Inversion Principle (depends on abstraction, not concrete classes)
331+
- Enables testing with mock factories
332+
- Prepares for jDisco→DSOL/Kalasim migration
333+
- See `CONTEXT_REFACTORING_DESIGN.md` and `FACTORY_PATTERN_IMPLEMENTATION.md` for details
334+
324335
**Object model:**
325336
- `objects/tracks/` - Track facilities, blocks, occupants
326337
- `objects/cells/` - Grid-based spatial representation (uses `Array2DMap`)
@@ -421,10 +432,14 @@ Koin modules are defined in `src/main/kotlin/cz/vutbr/fit/interlockSim/di/Interl
421432

422433
- **utilModule** - Utility classes (ready for expansion)
423434
- **xmlModule** - XML parsing, XMLContextFactory
424-
- **contextModule** - Context lifecycle management
435+
- **editingModule** - Editing context factories
436+
- **simulationModule** - Simulation context factories and SimulationProcessFactory
425437
- **guiModule** - Swing components (ready for expansion)
426438
- **objectsModule** - Domain model (minimal by design)
427-
- **sim/** - ❌ **EXCLUDED** (wait for jDisco migration)
439+
- **sim/** - ❌ **EXCLUDED** (wait for jDisco migration, except new factory classes)
440+
441+
**SimulationProcessFactory (2026-01-14):**
442+
The simulation module now provides `SimulationProcessFactory` as a singleton. This factory abstracts creation of simulation processes (Generator, InOutWorker) following the Factory pattern. Contexts receive the factory via constructor injection, eliminating direct dependencies on concrete sim/ classes.
428443

429444
### Critical DI Rules
430445

FACTORY_PATTERN_IMPLEMENTATION.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Factory Pattern Implementation - Summary
2+
3+
**Date:** 2026-01-14
4+
**Issue:** Divide DefaultContext to Editing and Simulation Implementation
5+
**Status:** Phase 2 Complete - Factory Pattern Implemented
6+
7+
## What Was Accomplished
8+
9+
### Problem Solved
10+
11+
DefaultContext was violating Dependency Inversion Principle by directly instantiating concrete simulation classes:
12+
13+
```kotlin
14+
// BEFORE - Direct instantiation (bad)
15+
if (mainProcess == null) mainProcess = Generator(this)
16+
for (i in inouts) {
17+
workers[i] = InOutWorker(this, i)
18+
}
19+
```
20+
21+
### Solution Implemented
22+
23+
Introduced Factory Pattern with Dependency Injection:
24+
25+
```kotlin
26+
// AFTER - Factory-based creation (good)
27+
if (mainProcess == null) {
28+
mainProcess = processFactory.createMainProcess(this)
29+
}
30+
for (i in inouts) {
31+
workers[i] = processFactory.createInOutWorker(this, i)
32+
}
33+
```
34+
35+
### Files Changed
36+
37+
1. **New: `SimulationProcessFactory.kt`** (context package)
38+
- Interface defining factory methods
39+
- No dependencies on concrete sim/ classes
40+
- Enables future simulation engine swapping
41+
42+
2. **New: `DefaultSimulationProcessFactory.kt`** (sim package)
43+
- Concrete factory implementation
44+
- Creates Generator and InOutWorker instances
45+
- Only place that needs to know about concrete simulation classes
46+
47+
3. **Modified: `DefaultContext.kt`**
48+
- Accepts `SimulationProcessFactory` via constructor
49+
- Uses factory to create simulation processes
50+
- Removed direct `Generator` import
51+
- Changed `setMainProcess(ShuntingLoop)``setMainProcess(LoopProcess)` for flexibility
52+
53+
4. **Modified: `XMLContextFactory.kt`**
54+
- Injects `SimulationProcessFactory` from Koin
55+
- Passes factory to DefaultContext constructor
56+
57+
5. **Modified: `InterlockSimModule.kt`** (DI configuration)
58+
- Added `SimulationProcessFactory` singleton binding
59+
- Factory instance provided to all contexts needing simulation
60+
61+
## Benefits Achieved
62+
63+
### 1. Dependency Inversion ✅
64+
- Context depends on abstraction (interface), not concrete classes
65+
- Can swap factory implementations without touching context code
66+
- Follows SOLID principles
67+
68+
### 2. Testability ✅
69+
- Can inject mock factory for testing
70+
- Test editing operations without simulation dependencies
71+
- Isolate simulation logic from context logic
72+
73+
### 3. Flexibility ✅
74+
- Easy to add new simulation process types
75+
- Ready for jDisco → DSOL/Kalasim migration
76+
- Custom factories for specialized simulations
77+
78+
### 4. Maintainability ✅
79+
- Clear separation of concerns
80+
- Factory pattern is well-known and documented
81+
- Centralized simulation object creation
82+
83+
## What Remains (Future Work)
84+
85+
### Phase 3: Class Splitting
86+
87+
Create separate classes for editing and simulation concerns:
88+
89+
```kotlin
90+
// Future architecture
91+
class DefaultEditingContext(...) : EditingContext {
92+
// Only editing operations
93+
// No simulation fields (mainProcess, workers, etc.)
94+
}
95+
96+
class DefaultSimulationContext(...) : DefaultEditingContext(...), SimulationContext {
97+
// Extends editing with simulation capabilities
98+
// Uses factory for process creation
99+
}
100+
```
101+
102+
**Benefits of split:**
103+
- Editing contexts can exist without any simulation code
104+
- Clearer which operations belong to which phase
105+
- Better alignment with domain model (editing vs running)
106+
107+
**Complexity:**
108+
- Large refactoring (DefaultContext is 984 lines)
109+
- Need to carefully split fields and methods
110+
- Must maintain backwards compatibility
111+
- Extensive testing required
112+
113+
### Phase 4: Static vs Dynamic Properties
114+
115+
Per issue comments, domain objects should eventually have:
116+
117+
```kotlin
118+
// Static properties (editing time)
119+
class Track(val length: Double, val maxSpeed: Double)
120+
121+
// Dynamic properties (simulation time)
122+
class DynamicTrack(val staticTrack: Track) {
123+
var currentOccupant: Train? = null
124+
var isOccupied: Boolean = false
125+
}
126+
```
127+
128+
This is a larger refactoring and should be a separate issue.
129+
130+
## Design Decisions
131+
132+
### Why Factory in sim/ Package?
133+
134+
**Decision:** Place `DefaultSimulationProcessFactory` in sim/ package.
135+
136+
**Rationale:**
137+
- It's the only place that needs concrete sim/ class knowledge
138+
- Keeps simulation implementation details in simulation package
139+
- Factory interface in context/ (abstraction) vs implementation in sim/ (concrete)
140+
141+
### Why Not Split Classes Now?
142+
143+
**Decision:** Defer class splitting to Phase 3 (future PR).
144+
145+
**Rationale:**
146+
- Conservative approach - make minimal changes
147+
- Factory pattern already solves the main SOLID violations
148+
- Large refactoring requires extensive testing
149+
- Gradual approach reduces risk
150+
151+
### Why Keep InOutWorker Import?
152+
153+
**Decision:** Keep `InOutWorker` and `LoopProcess` imports in DefaultContext.
154+
155+
**Rationale:**
156+
- Required for interface method signatures (`getWorkerFor(): InOutWorker`)
157+
- Used in field types (`workers: Map<InOut, InOutWorker>`)
158+
- Not instantiated directly - creation delegated to factory
159+
- Type safety maintained
160+
161+
## Migration Guide
162+
163+
### For Custom Factories
164+
165+
If you want to create a custom simulation factory:
166+
167+
```kotlin
168+
class CustomSimulationProcessFactory : SimulationProcessFactory {
169+
override fun createMainProcess(context: SimulationContext): LoopProcess {
170+
return MyCustomGenerator(context)
171+
}
172+
173+
override fun createInOutWorker(context: SimulationContext, inOut: InOut): InOutWorker {
174+
return MyCustomWorker(context, inOut)
175+
}
176+
}
177+
178+
// In DI configuration
179+
val simulationModule = module {
180+
single<SimulationProcessFactory> { CustomSimulationProcessFactory() }
181+
}
182+
```
183+
184+
### For jDisco → DSOL Migration
185+
186+
When migrating to DSOL:
187+
188+
1. Create `DSOLSimulationProcessFactory` implementing `SimulationProcessFactory`
189+
2. Update return types if DSOL process types differ
190+
3. Update Koin configuration to use DSOL factory
191+
4. All context code continues to work unchanged
192+
193+
## Testing Strategy
194+
195+
### Unit Tests Needed
196+
197+
1. **SimulationProcessFactory Tests**
198+
```kotlin
199+
@Test
200+
fun `factory creates Generator`() {
201+
val factory = DefaultSimulationProcessFactory()
202+
val process = factory.createMainProcess(mockContext)
203+
assertThat(process).isInstanceOf<Generator>()
204+
}
205+
```
206+
207+
2. **Context with Mock Factory**
208+
```kotlin
209+
@Test
210+
fun `context uses factory for process creation`() {
211+
val mockFactory = mock<SimulationProcessFactory>()
212+
val context = XMLContext(10, 10, mockFactory)
213+
context.run()
214+
verify(mockFactory).createMainProcess(context)
215+
}
216+
```
217+
218+
### Integration Tests Needed
219+
220+
1. Existing simulation examples still work
221+
2. All 662 tests pass
222+
3. XML loading and simulation execution unchanged
223+
224+
## Conclusion
225+
226+
Phase 2 successfully implemented the Factory pattern, addressing the core SOLID violations in DefaultContext. The code is now:
227+
228+
- ✅ More testable
229+
- ✅ More flexible
230+
- ✅ More maintainable
231+
- ✅ Ready for future refactoring
232+
- ✅ Ready for jDisco migration
233+
234+
The full class split (Phase 3) can be done incrementally in future PRs once this foundation is validated through testing.
235+
236+
## References
237+
238+
- Design Document: `CONTEXT_REFACTORING_DESIGN.md`
239+
- Issue: "Divide DefaultContext to Editing and Simulation implementation"
240+
- Pattern: Factory Method (Gang of Four)
241+
- DI Framework: Koin (https://insert-koin.io/)

0 commit comments

Comments
 (0)