Dependency Injection Implementation in TypeScript
Dependency injection (DI) is a powerful design pattern that promotes loose coupling and testability in software. This challenge asks you to implement different types of dependency injection – constructor injection, property injection, and method injection – in TypeScript, demonstrating your understanding of this crucial concept. Successfully completing this challenge will allow you to build more modular and maintainable applications.
Problem Description
You are tasked with creating a TypeScript module that showcases three common dependency injection patterns: constructor injection, property injection, and method injection. The module should define an interface ILogger with a single method log(message: string) and then implement three classes: ServiceA, ServiceB, and ServiceC. Each service should demonstrate a different DI pattern to receive an instance of ILogger.
What needs to be achieved:
- Define the
ILoggerinterface. - Create a concrete
Loggerclass that implementsILogger. - Implement
ServiceAusing constructor injection. - Implement
ServiceBusing property injection. - Implement
ServiceCusing method injection. - Provide a main function that instantiates each service, injects the
Loggerinstance, and calls a method on each service that utilizes the injected logger.
Key Requirements:
- The code must be written in TypeScript.
- Each service must correctly utilize the injected logger.
- The code should be well-structured and readable.
- Demonstrate the three DI patterns clearly and distinctly.
Expected Behavior:
When the main function is executed, each service should log a message to the console using its injected logger. The messages should be distinct for each service to confirm that the dependency injection is working correctly.
Edge Cases to Consider:
- What happens if a service is instantiated without the required dependency? (While not strictly required to handle this in the solution, consider it for robustness).
- How does each injection type affect the testability of the service? (This is a conceptual consideration, not a coding requirement).
Examples
Example 1:
Input: No specific input, the code will be executed directly.
Output:
ServiceA: Logging message from ServiceA.
ServiceB: Logging message from ServiceB.
ServiceC: Logging message from ServiceC.
Explanation: Each service successfully receives and uses the injected logger.
Example 2:
Input: A different message to be logged by the Logger.
Output:
ServiceA: Logging [new message] from ServiceA.
ServiceB: Logging [new message] from ServiceB.
ServiceC: Logging [new message] from ServiceC.
Explanation: The logger's message is updated, and all services reflect this change.
Constraints
- The code must be valid TypeScript.
- The
logmethod inILoggershould accept a string as input. - The solution should be concise and demonstrate the core concepts of each DI pattern.
- No external libraries are allowed.
Notes
- Constructor injection is generally considered the preferred method as it makes dependencies explicit.
- Property injection can be useful for optional dependencies.
- Method injection is less common but can be useful in specific scenarios.
- Focus on demonstrating the concept of each DI pattern rather than complex error handling or advanced features. The goal is to show you understand how to inject dependencies using different approaches.
- Think about how each injection type might impact unit testing.