제이온

[Effective Java] Item 5: Prefer Dependency Injection to Hardwiring Resources

Created: 2024-04-28

Created: 2024-04-28 13:39

If a class relies on one or more internal resources, and those resources influence the class's behavior, it's best to avoid using singleton and static utility classes.


The class shouldn't be responsible for creating these resources directly. Instead, it's preferable to pass the necessary resources to the constructor. Dependency injection enhances the class's flexibility, reusability, and testability.


Example

Example using a Static Utility Class

This utility class assumes that only one dictionary is used. However, in reality, dictionaries might be separated by language, and there might even be separate dictionaries for specialized vocabulary.


Example using a Singleton Class


Similarly, singleton classes assume the use of only one dictionary, leading to the same drawbacks as mentioned above.


Solution 1 - Remove the `final` Keyword from the Field


For the `dictionary` in a static utility class or singleton class, you can remove the `final` keyword and design it to allow external replacement of the `dictionary` with a different one. However, this approach is awkward to use and can lead to concurrency issues in multithreaded environments.


Solution 2 - Use Dependency Injection


Through this example, we can understand that static classes and singleton classes should not depend on internal resources. In other words, it's better to inject internal resources from the outside.


Classes using dependency injection offer the advantage of immutability thanks to the `final` keyword, and they support multiple resource instances. Furthermore, dependency injection can be applied not only through constructors but also through static factories and builders.


While dependency injection can involve simply passing the resource itself, it often utilizes a resource factory. A factory is an object that repeatedly creates instances of a specific type whenever called. This approach is known as the Factory Method pattern, and `Supplier<T>` in Java 8 is a perfect example representing a factory.



The type parameter of the factory is often restricted using a bounded wildcard type. This approach allows clients to pass any factory as long as it produces a subtype of the type specified by the client.


Dependency injection enhances flexibility and testability, but it can become quite costly in projects with many dependencies. In such cases, dependency injection frameworks (Dagger, Guice, Spring, etc.) can be used to reduce the overhead.


Source

Comments0