![translation](https://cdn.durumis.com/common/trans.png)
This is an AI translated post.
[Effective Java] Item 5. Use Dependency Injection instead of Hard-Coding Resources
- Writing language: Korean
- •
-
Base country: All countries
- •
- Information Technology
Select Language
Summarized by durumis AI
- If a class internally relies on one or more resources, it is not recommended to use static utility classes and singleton classes, and it is desirable to use dependency injection.
- Using dependency injection can improve the class's flexibility, reusability, and testability, and resources can be injected from constructors, static factories, builders, etc.
- Dependency injection can be used by passing the resource itself or the resource factory, and using a dependency injection framework is efficient for projects with many dependencies.
If a class internally relies on one or more resources, and those resources affect the class's behavior, it is best to avoid using singletons and static utility classes.
The class should not create these resources directly, instead, it is better to pass the necessary resources to the constructor. Dependency injection can improve the flexibility, reusability, and testability of the class.
Example
Example of using a static utility class
public class SpellChecker {
private static final Lexicon dictionary = new Lexicon();
private SpellChecker() {
}
public static boolean isValid(String word) {
// Logic using dictionary
}
public static List suggestions(String typo) {
// Logic using dictionary
}
This utility class assumes that only one dictionary is used. However, in reality, dictionaries are often separated by language, and even specialized dictionaries are used separately for special vocabulary.
Example of using a singleton class
public class SpellChecker {
private final Lexicon dictionary = new Lexicon();
public static SpellChecker INSTANCE = new SpellChecker();
private SpellChecker() {
}
public static boolean isValid(String word) {
// Logic using dictionary
}
public static List suggestions(String typo) {
// Logic using dictionary
}
Similarly, singleton classes assume that only one dictionary is used, resulting in the same drawback as above.
Solution 1 - Remove the final keyword from the field.
public class SpellChecker {
private Lexicon dictionary = new Lexicon();
public static SpellChecker INSTANCE = new SpellChecker();
private SpellChecker() {
}
public static void changeDictionary(Lexicon dictionary) {
this.dictionary = dictionary;
}
public static boolean isValid(String word) {
// Logic using dictionary
}
public static List suggestions(String typo) {
// Logic using dictionary
}
It is possible to remove the final keyword from the dictionary of a static utility class or singleton class and design it so that the dictionary can be replaced with a different dictionary from the outside. However, this method is awkward to use, and can cause concurrency issues in a multi-threaded environment.
Solution 2 - Use dependency injection.
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = dictionary;
}
public static boolean isValid(String word) {
// Logic using dictionary
}
public static List suggestions(String typo) {
// Logic using dictionary
}
Through the example above, we can see that static classes and singleton classes should not rely on internal resources. In other words, it is desirable to receive internal resources from the outside.
Classes that use dependency injection have the advantage of guaranteeing immutability thanks to the final keyword and supporting multiple resource instances.
Dependency injection can be applied not only to constructors but also to static factories and builders.
Dependency injection simply passes the resource itself, but it is also often used to pass a resource factory. A factory is an object that creates instances of a specific type repeatedly when called. This method is called the factory method pattern, and Supplier
public static List create(Supplier extends Car> generator) {
...
Usually, a bounded wildcard type is used to limit the type parameter of the factory. Using this method, the client can pass any factory that is a subtype of the type it specifies.
Dependency injection improves flexibility and testability, but it can be very expensive for projects with many dependencies. In such cases, the cost can be reduced by using dependency injection frameworks (Dagger, Guice, Spring, etc.).