文章目录
- Effective第三版
- 前言
- 第二章 创建和销毁对象
- 固定资源首选使用依赖注入
Effective第三版
前言
大家好,这里是 Rocky 编程日记 ,喜欢后端架构及中间件源码,目前正在阅读 effective-java 书籍。同时也把自己学习该书时的笔记,代码分享出来,供大家学习交流,如若笔记中有不对的地方,那一定是当时我的理解还不够,希望你能及时提出。如果对于该笔记存在很多疑惑,欢迎和我交流讨论,最后也感谢您的阅读,点赞,关注,收藏~
前人述备矣,我只是知识的搬运工,effective 书籍源码均在开源项目 java-diary 中的 code-effective-third 模块中
源代码仓库地址: https://gitee.com/Rocky-BCRJ/java-diary.git
第二章 创建和销毁对象
固定资源首选使用依赖注入
Many classes depend on one or more underlying resources. For example, a spell checker depends on a dictionary. It is not uncommon to see such classes implemented as static utility classes(Item 4):
许多类依赖于一个或多个底层资源。例如,拼写检查器依赖于字典。常见的做法是将这些类实现为静态实用程序类(第 4 项):
// Inappropriate use of static utility - inflexible & untestable!
public class SpellChecker {private static final Lexicon dictionary = ...;private SpellChecker() {} // Noninstantiablepublic static boolean isValid(String word) { ... }public static List<String> suggestions(String typo) { ... }
}
Similarly, it’s not uncommon to see them implemented as singletons (Item 3):
同样的,将它们作为单例实现的情况并不少见(第 3 项):
// Inappropriate use of singleton - inflexible & untestable!
public class SpellChecker {private final Lexicon dictionary = ...;private SpellChecker(...) {}public static INSTANCE = new SpellChecker(...);public boolean isValid(String word) { ... }public List<String> suggestions(String typo) { ... }
}
Neither of these approaches is satisfactory, because they assume that there is only one dictionary worth using. In practice, each language has its own dictionary, and special dictionaries are used for special vocabularies. Also, it may be desirable to use a special dictionary for testing. It is wishful thinking to assume that a single dictionary will suffice for all time.
这些方法都不令人满意,因为它们假设只有一本值得使用的字典。在实践中,每种语言都有自己的字典,特殊字典用于特殊词汇。而且,可能需要使用特殊字典进行测试。假设单本字典就足以满足所有情况,这是一厢情愿的想法。
You could try to have SpellChecker support multiple dictionaries by making the dictionary field nonfinal and adding a method to change the dictionary in an existing spell checker, but this would be awkward, error-prone, and unworkable in a concurrent setting. Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.
你可以尝试让 SpellChecker 支持多个词典,方法是使字典字段为非 final 域,并添加一个方法来更改现有拼写检查器中的字典,但这在并发时设置会很笨拙,容易出错并且不可行。静态实用程序类和单例不适用于将底层资源作为参数的类(Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.)。
What is required is the ability to support multiple instances of the class (in our example, SpellChecker), each of which uses the resource desired by the client (in our example, the dictionary). A simple pattern that satisfies this requirement is to pass the resource into the constructor when creating a new instance. This is one form of dependency injection: the dictionary is a dependency of the spell checker and is injected into the spell checker when it is created.
所需要的是能够支持类的多个实例(在我们的示例中为 SpellChecker),每个实例都使用客户端所需的资源(在我们的示例中为字典)。满足此要求的简单模式是在创建新实例时将资源传递给构造函数。这是依赖注入的一种形式:字典是拼写检查器的依赖项,并在创建时注入拼写检查器。
// Dependency injection provides flexibility and testability
public class SpellChecker {private final Lexicon dictionary;public SpellChecker(Lexicon dictionary) {this.dictionary = Objects.requireNonNull(dictionary);}public boolean isValid(String word) { ... }public List<String> suggestions(String typo) { ... }
}
The dependency injection pattern is so simple that many programmers use it for years without knowing it has a name. While our spell checker example had only a single resource (the dictionary), dependency injection works with an arbitrary number of resources and arbitrary dependency graphs. It preserves immutability (Item 17), so multiple clients can share dependent objects (assuming the clients desire the same underlying resources). Dependency injection is equally applicable to constructors, static factories (Item 1), and builders (Item 2).
这种依赖注入很简单,以至于程序猿用了很多年却不知道它有一个名称。虽然我们的拼写检查器只有一个资源(字典),但是依赖注入可以使用任意数量的资源和任意的依赖关系,它保留了不变性(第 17 项),因此多个客户端可以共享依赖对象(假设客户端需要相同的底层资源)。依赖注入同样适用于构造函数、静态工厂(第 1 项)和构建器(第 2 项)。
A useful variant of the pattern is to pass a resource factory _to the constructor. A factory is an object that can be called repeatedly to create instances of a type. Such factories embody the _Factory Method pattern [Gamma95]. The Supplier interface, introduced in Java 8, is perfect for representing factories. Methods that take a Supplier on input should typically constrain the factory’s type parameter using a bounded wildcard type (Item 31) to allow the client to pass in a factory that creates any subtype of a specified type. For example, here is a method that makes a mosaic using a client-provided factory to produce each tile:
将资源工厂传递给构造函数就会变成一个有用的模式。工厂是一个对象,通过重复调用这个工厂可以创建某个类型的实例对象。这些就是工厂方法模式 [Gamma95]。Java 8 中引入的 Supplier<T>;接口非常适合体现工厂。在输入上采用 Supplier<T>的方法通常应该使用泛型(第 31 项)约束工厂的类型参数,以允许客户端传入创建指定类型的任何子类型的工厂。例如,这是一种使用客户提供的工厂生成马赛克来生成每个图块的方法:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
Although dependency injection greatly improves flexibility and testability, it can clutter up large projects, which typically contain thousands of dependencies. This clutter can be all but eliminated by using a dependency injection framework, such as Dagger [Dagger], Guice [Guice], or Spring [Spring]. The use of these frameworks is beyond the scope of this book, but note that APIs designed for manual dependency injection are trivially adapted for use by these frameworks.
尽管依赖注入极大地提高了灵活性和可测试性,但它可能会使大型项目更加混乱,这些项目通常包含数千个依赖项。通过使用依赖注入框架,例如 Dagger [Dagger],Guice [Guice]或 Spring [Spring],可以消除这种混乱。这些框架的使用超出了本书的范围,但请注意,为手动依赖注入而设计的 API 可以轻松地适用于这些框架。
In summary, do not use a singleton or static utility class to implement a class that depends on one or more underlying resources whose behavior affects that of the class, and do not have the class create these resources directly. Instead, pass the resources, or factories to create them, into the constructor (or static factory or builder). This practice, known as dependency injection, will greatly enhance the flexibility, reusability, and testability of a class.
总之,如果有一个类依赖一个或多个底层资源的类,并且底层资源类影响了类的行为,不要使用单例或静态实用程序类来实现它,并且不要让类直接创建这些资源(do not use a singleton or static utility class to implement a class that depends on one or more underlying resources whose behavior affects that of the class)。相反,将资源或工厂传递给构造函数(或静态工厂或构建器)。这种做法称为依赖注入,将极大地增强类的灵活性,可重用性和可测试性。