Duplicate Interfaces Example
This example demonstrates how IoC Arise handles the error case where multiple classes implement the same interface. You’ll learn why this creates ambiguity for dependency injection and how IoC Arise provides clear error messages to help you resolve these conflicts.
Project Structure
Section titled “Project Structure”Directoryduplicate-interfaces-example/
- ioc.config.json
Directoryservices/
- INotificationService.ts
- EmailNotificationService.ts
- SmsNotificationService.ts
- PushNotificationService.ts
The Problem
Section titled “The Problem”- Single Interface:
INotificationService
defines the contract for notification services - Multiple Implementations: Three different classes implement the same interface
- Ambiguity Problem: This creates ambiguity for dependency injection - which implementation should be used?
- Error Detection: IoC Arise detects this and throws a clear error to prevent runtime issues
Interface Definition
Section titled “Interface Definition”export interface INotificationService { sendNotification(message: string, recipient: string): Promise<void>; getServiceName(): string;}
Multiple Implementations
Section titled “Multiple Implementations”EmailNotificationService
Section titled “EmailNotificationService”import { INotificationService } from './INotificationService';
export class EmailNotificationService implements INotificationService { async sendNotification(message: string, recipient: string): Promise<void> { console.log(`Sending EMAIL notification to ${recipient}: ${message}`); await new Promise(resolve => setTimeout(resolve, 100)); }
getServiceName(): string { return 'Email Notification Service'; }}
SmsNotificationService
Section titled “SmsNotificationService”import { INotificationService } from './INotificationService';
export class SmsNotificationService implements INotificationService { async sendNotification(message: string, recipient: string): Promise<void> { console.log(`Sending SMS notification to ${recipient}: ${message}`); await new Promise(resolve => setTimeout(resolve, 50)); }
getServiceName(): string { return 'SMS Notification Service'; }}
PushNotificationService
Section titled “PushNotificationService”import { INotificationService } from './INotificationService';
export class PushNotificationService implements INotificationService { async sendNotification(message: string, recipient: string): Promise<void> { console.log(`Sending PUSH notification to ${recipient}: ${message}`); await new Promise(resolve => setTimeout(resolve, 25)); }
getServiceName(): string { return 'Push Notification Service'; }}
Expected Behavior
Section titled “Expected Behavior”When running npx @notjustcoders/ioc-arise generate
, the tool should:
- Scan all classes and detect interface implementations
- Identify that
INotificationService
is implemented by multiple classes - Log detailed error information showing which classes implement the duplicate interface
- Throw an error with a clear message about the duplicate implementations
- Stop the generation process to prevent ambiguous dependency injection
Expected Error Message
Section titled “Expected Error Message”Error: Interface 'INotificationService' is implemented by multiple classes: - EmailNotificationService (/path/to/services/EmailNotificationService.ts) - SmsNotificationService (/path/to/services/SmsNotificationService.ts) - PushNotificationService (/path/to/services/PushNotificationService.ts)
Multiple classes implement the same interface(s): INotificationService. Each interface should only be implemented by one class for proper dependency injection.
Why This Happens
Section titled “Why This Happens”IoC Arise follows the principle that each interface should have exactly one implementation for dependency injection to work correctly. When multiple classes implement the same interface, the container doesn’t know which one to inject.
Resolution Strategies
Section titled “Resolution Strategies”To fix this issue, you have several options:
Option 1: Use Factory Pattern
Section titled “Option 1: Use Factory Pattern”Create a factory class that decides which implementation to use:
export interface INotificationFactory { createEmailService(): INotificationService; createSmsService(): INotificationService; createPushService(): INotificationService;}
Option 2: Use Specific Interfaces
Section titled “Option 2: Use Specific Interfaces”Create specific interfaces for each implementation:
export interface IEmailNotificationService extends INotificationService {}export interface ISmsNotificationService extends INotificationService {}export interface IPushNotificationService extends INotificationService {}
Option 3: Use Modules with Different Names
Section titled “Option 3: Use Modules with Different Names”Organize implementations into different modules or use different naming patterns to avoid conflicts.
Configuration
Section titled “Configuration”{ "source": ".", "output": "container.gen.ts", "interface": "I[A-Z].*", "exclude": [ "**/*.test.ts", "**/*.spec.ts" ], "verbose": true, "modules": { "DuplicateModule": [ "services/**" ] }}
Running the Example
Section titled “Running the Example”-
Try to generate the container (this should fail):
Terminal window npx @notjustcoders/ioc-arise generate -
The command should exit with an error and detailed information about the duplicate implementations
Post-Construction Initialization
Section titled “Post-Construction Initialization”The onInit()
function is exported from the generated container.gen.ts file, not a method in your classes:
import { onInit } from './container.gen';
// The onInit function is called automatically when inject() is first used// You can modify it in the generated container.gen.ts file for custom initialization
If the interfaces were properly separated, here’s how you would use post-construction dependency injection:
export class NotificationManager { private emailService!: IEmailNotificationService; private smsService!: ISmsNotificationService; private pushService!: IPushNotificationService;
initializeServices() { this.emailService = inject('duplicateModule.IEmailNotificationService'); this.smsService = inject('duplicateModule.ISmsNotificationService'); this.pushService = inject('duplicateModule.IPushNotificationService'); console.log('NotificationManager initialized with all notification services'); }
async sendToAll(message: string, recipient: string) { await Promise.all([ this.emailService.sendNotification(message, recipient), this.smsService.sendNotification(message, recipient), this.pushService.sendNotification(message, recipient) ]); }}
Benefits of Duplicate Detection
Section titled “Benefits of Duplicate Detection”- Error Prevention: Catches ambiguous dependency injection at build time
- Clear Error Messages: Detailed reporting of which classes cause conflicts
- Architecture Validation: Ensures clean dependency injection patterns
- Type Safety: Maintains full TypeScript support with proper interface contracts
- Development Guidance: Provides clear paths to resolve conflicts
This example demonstrates how IoC Arise’s strict validation prevents runtime ambiguity and guides you toward better architectural decisions by catching interface conflicts early in the development process!