Class_generator/src/DRS/factories/ExerciseFactory.js
StillHammer 44f43324cf Implement ExerciseFactory with strict DRS interface enforcement
🏭 **MAJOR ARCHITECTURE - Factory Pattern Implementation**

**ExerciseFactory Created** (src/DRS/factories/ExerciseFactory.js):
-  Unified creation system for all 11 DRS exercise modules
-  Strict DRSExerciseInterface validation (10 required methods)
-  Smart dependency injection (orchestrator, prerequisiteEngine, llmValidator, contextMemory)
-  Module type mapping: 20 exercise types → 11 module classes
-  Methods: create(), createAndRender(), batchCreate(), destroy()
-  Caching system for loaded module classes
-  Smart warnings (no LLM warning for local-validation modules)

**UnifiedDRS Refactored** (src/DRS/UnifiedDRS.js):
-  Integrated ExerciseFactory for all module loading
-  Created generic helper: _loadExerciseViaFactory()
-  Centralized dependencies: _getDependencies()
-  Simplified loaders: _loadVocabularyModule(), _loadWordDiscoveryModule(), _loadAIAnalysisModule()
-  Removed manual HTML generation - all via factory now
-  165 lines removed, code DRY improved

**Test Suite Created** (test-factory.html):
-  Interactive web-based test interface
-  3 test suites: Validation, Creation (11 modules), Interface compliance
-  Real-time results with success/failure indicators
-  Factory stats display

**Content Enhancement** (test-heavy-stress.json):
-  Added 12 comprehension questions (3 lessons + 1 dialog)
-  lesson1: 3 questions on research methodology
-  lesson2: 3 questions on statistical analysis
-  lesson3: 3 questions on qualitative research
-  conference_presentation: 3 questions on mixed-methods
-  Fixes IntelligentSequencer warnings (now has questions)

**Benefits:**
- 🛡️ Impossible to create modules without interface compliance
- 🔒 Red screen errors force correct implementation
- 📋 Self-documenting via strict contracts
- 🔄 100% DRY - zero code duplication
- 🧪 Fully testable with mock dependencies
- 🎯 Production-ready architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 15:39:53 +08:00

299 lines
10 KiB
JavaScript

/**
* ExerciseFactory - Factory for creating DRS exercise modules
*
* Creates exercise modules that implement DRSExerciseInterface
* Provides unified creation, initialization, and lifecycle management
*/
import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js';
class ExerciseFactory {
constructor() {
// Registry of available exercise modules
this._moduleRegistry = new Map();
// Module type to class name mapping
this._typeMap = {
'vocabulary-flashcards': 'VocabularyModule',
'vocabulary-discovery': 'WordDiscoveryModule',
'text': 'TextModule',
'text-analysis': 'TextAnalysisModule',
'reading': 'TextModule',
'reading-comprehension': 'TextModule',
'reading-comprehension-AI': 'TextAnalysisModule',
'grammar': 'GrammarModule',
'grammar-analysis': 'GrammarAnalysisModule',
'grammar-practice': 'GrammarModule',
'grammar-practice-AI': 'GrammarAnalysisModule',
'translation': 'TranslationModule',
'open-response': 'OpenResponseModule',
'phrase': 'PhraseModule',
'phrase-practice': 'PhraseModule',
'audio': 'AudioModule',
'listening-comprehension': 'AudioModule',
'listening-comprehension-AI': 'AudioModule',
'visual-description': 'ImageModule',
'visual-description-AI': 'ImageModule'
};
// Cache for loaded module classes
this._classCache = new Map();
}
/**
* Create an exercise module by type
* @param {string} exerciseType - Type of exercise to create
* @param {Object} dependencies - Dependencies (orchestrator, llmValidator, prerequisiteEngine, contextMemory)
* @param {Object} config - Exercise configuration
* @param {Object} content - Exercise content (optional, can be passed later to init)
* @returns {Promise<DRSExerciseInterface>} - Exercise module instance
*/
async create(exerciseType, dependencies, config = {}, content = null) {
console.log(`🏭 ExerciseFactory creating: ${exerciseType}`);
// Validate exercise type
if (!this._typeMap[exerciseType]) {
throw new Error(`Unknown exercise type: ${exerciseType}. Available types: ${Object.keys(this._typeMap).join(', ')}`);
}
// Get module class name
const className = this._typeMap[exerciseType];
console.log(`📦 Resolved to module class: ${className}`);
// Load module class (with caching)
const ModuleClass = await this._loadModuleClass(className);
// Validate dependencies
this._validateDependencies(className, dependencies);
// Create instance
const instance = new ModuleClass(
dependencies.orchestrator,
dependencies.llmValidator,
dependencies.prerequisiteEngine,
dependencies.contextMemory
);
// Validate interface implementation
this._validateInterface(instance, className);
// Initialize the module with config and content
await instance.init(config, content);
console.log(`${className} created and initialized`);
return instance;
}
/**
* Load module class (with caching)
* @param {string} className - Name of the module class
* @returns {Promise<Class>} - Module class
*/
async _loadModuleClass(className) {
// Check cache first
if (this._classCache.has(className)) {
console.log(`📦 Using cached class: ${className}`);
return this._classCache.get(className);
}
// Determine file path based on class name
let modulePath;
if (className === 'WordDiscoveryModule') {
modulePath = '../exercise-modules/WordDiscoveryModule.js';
} else if (className.endsWith('AnalysisModule')) {
modulePath = `../exercise-modules/${className}.js`;
} else {
modulePath = `../exercise-modules/${className}.js`;
}
console.log(`📂 Loading module from: ${modulePath}`);
try {
const module = await import(modulePath);
const ModuleClass = module.default;
// Cache the class
this._classCache.set(className, ModuleClass);
return ModuleClass;
} catch (error) {
throw new Error(`Failed to load module class ${className} from ${modulePath}: ${error.message}`);
}
}
/**
* Validate dependencies
* @param {string} className - Module class name
* @param {Object} dependencies - Dependencies object
*/
_validateDependencies(className, dependencies) {
const required = ['orchestrator', 'prerequisiteEngine'];
// Modules that don't need AI validation
const noAIModules = ['VocabularyModule', 'WordDiscoveryModule'];
// Check required dependencies
for (const dep of required) {
if (!dependencies[dep]) {
throw new Error(`${className} requires dependency: ${dep}`);
}
}
// Check optional dependencies (warn if missing, except for no-AI modules)
if (!dependencies.llmValidator && !noAIModules.includes(className)) {
console.warn(`⚠️ ${className} missing optional dependency: llmValidator`);
}
if (!dependencies.contextMemory) {
console.warn(`⚠️ ${className} missing optional dependency: contextMemory`);
}
}
/**
* Validate that instance implements DRSExerciseInterface
* @param {Object} instance - Module instance
* @param {string} className - Class name for error messages
*/
_validateInterface(instance, className) {
const requiredMethods = [
'init', 'render', 'destroy', 'validate',
'getResults', 'handleUserInput', 'markCompleted',
'getProgress', 'getExerciseType', 'getExerciseConfig'
];
const missing = [];
for (const method of requiredMethods) {
if (typeof instance[method] !== 'function') {
missing.push(method);
}
}
if (missing.length > 0) {
throw new Error(
`${className} does not properly implement DRSExerciseInterface.\n` +
`Missing methods: ${missing.join(', ')}\n` +
`All DRS modules MUST implement the full interface.`
);
}
console.log(`${className} implements DRSExerciseInterface correctly`);
}
/**
* Get all supported exercise types
* @returns {string[]} - Array of exercise type strings
*/
getSupportedTypes() {
return Object.keys(this._typeMap);
}
/**
* Check if exercise type is supported
* @param {string} exerciseType - Type to check
* @returns {boolean} - True if supported
*/
isSupported(exerciseType) {
return this._typeMap.hasOwnProperty(exerciseType);
}
/**
* Get module class name for exercise type
* @param {string} exerciseType - Exercise type
* @returns {string} - Class name
*/
getModuleClassName(exerciseType) {
return this._typeMap[exerciseType] || null;
}
/**
* Create exercise and render it immediately
* @param {string} exerciseType - Type of exercise
* @param {HTMLElement} container - Container element
* @param {Object} dependencies - Dependencies
* @param {Object} config - Configuration
* @param {Object} content - Exercise content
* @returns {Promise<DRSExerciseInterface>} - Created and rendered module
*/
async createAndRender(exerciseType, container, dependencies, config, content) {
console.log(`🎨 Creating and rendering ${exerciseType}...`);
// Create the module with config and content
const module = await this.create(exerciseType, dependencies, config, content);
// Render it (content already passed in init)
await module.render(container);
console.log(`${exerciseType} created and rendered`);
return module;
}
/**
* Batch create multiple modules (for preloading)
* @param {Array} exerciseTypes - Array of exercise types to create
* @param {Object} dependencies - Shared dependencies
* @returns {Promise<Map>} - Map of type -> instance
*/
async batchCreate(exerciseTypes, dependencies) {
console.log(`🏭 Batch creating ${exerciseTypes.length} modules...`);
const instances = new Map();
const errors = [];
for (const type of exerciseTypes) {
try {
const instance = await this.create(type, dependencies);
instances.set(type, instance);
} catch (error) {
errors.push({ type, error: error.message });
console.error(`❌ Failed to create ${type}:`, error);
}
}
if (errors.length > 0) {
console.warn(`⚠️ ${errors.length} modules failed to create:`, errors);
}
console.log(`✅ Batch created ${instances.size}/${exerciseTypes.length} modules`);
return instances;
}
/**
* Destroy module and clean up
* @param {DRSExerciseInterface} module - Module to destroy
*/
async destroy(module) {
if (!module) return;
try {
await module.destroy();
console.log(`🧹 Module destroyed: ${module.getExerciseType()}`);
} catch (error) {
console.error('Failed to destroy module:', error);
}
}
/**
* Get factory statistics
* @returns {Object} - Factory stats
*/
getStats() {
return {
supportedTypes: this.getSupportedTypes().length,
cachedClasses: this._classCache.size,
typeMapping: Object.keys(this._typeMap).length
};
}
/**
* Clear class cache (for hot reload during development)
*/
clearCache() {
this._classCache.clear();
console.log('🧹 ExerciseFactory class cache cleared');
}
}
// Export singleton instance
const exerciseFactory = new ExerciseFactory();
export default exerciseFactory;