Skip to content

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.

  • Directoryduplicate-interfaces-example/
    • ioc.config.json
    • Directoryservices/
      • INotificationService.ts
      • EmailNotificationService.ts
      • SmsNotificationService.ts
      • PushNotificationService.ts
  1. Single Interface: INotificationService defines the contract for notification services
  2. Multiple Implementations: Three different classes implement the same interface
  3. Ambiguity Problem: This creates ambiguity for dependency injection - which implementation should be used?
  4. Error Detection: IoC Arise detects this and throws a clear error to prevent runtime issues
services/INotificationService.ts
export interface INotificationService {
sendNotification(message: string, recipient: string): Promise<void>;
getServiceName(): string;
}
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';
}
}
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';
}
}
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';
}
}

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
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.

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.

To fix this issue, you have several options:

Create a factory class that decides which implementation to use:

export interface INotificationFactory {
createEmailService(): INotificationService;
createSmsService(): INotificationService;
createPushService(): INotificationService;
}

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.

{
"source": ".",
"output": "container.gen.ts",
"interface": "I[A-Z].*",
"exclude": [
"**/*.test.ts",
"**/*.spec.ts"
],
"verbose": true,
"modules": {
"DuplicateModule": [
"services/**"
]
}
}
  1. Try to generate the container (this should fail):

    Terminal window
    npx @notjustcoders/ioc-arise generate
  2. The command should exit with an error and detailed information about the duplicate implementations

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)
]);
}
}
  1. Error Prevention: Catches ambiguous dependency injection at build time
  2. Clear Error Messages: Detailed reporting of which classes cause conflicts
  3. Architecture Validation: Ensures clean dependency injection patterns
  4. Type Safety: Maintains full TypeScript support with proper interface contracts
  5. 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!