使用依赖注入取代硬连接资源

This commit is contained in:
尉勇强2 2019-09-16 20:17:17 +08:00
parent f1df6a3327
commit d6857ddc4c
4 changed files with 140 additions and 0 deletions

View File

@ -0,0 +1,33 @@
## 使用私有构造方法执行非实例化
### 示例
- [Item04Example01.java](CreatingAndDestroyingObjects/src/main/java/com/jueee/item04/Item04Example01.java):禁止默认构造函数以实现非实例化
### 说明
偶尔你会想写一个类,它只是一组静态方法和静态属性。
这样的类获得了不好的名声,因为有些人滥用这些类而避免以面向对象方式思考,但是它们确实有着特殊的用途。
它们可以用来按照`java.lang.Math`或`java.util.Arrays`的方式,在基本类型的数值或数组上组织相关的方法。 它们也可以用于将静态方法(包括工厂(条目 1分组用于实现某个接口的对象其方式为`java.util.Collections`。
最后这样的类可以用于在final类上对方法进行分组因为不能将它们放在子类中。
这样的实用类( utility classes不是设计用来被实例化的一个实例是没有意义的。然而在没有显式构造方法的情况下编译器提供了一个公共的、无参的默认构造方法。对于用户来说该构造方法与其他构造方法没有什么区别。在已发布的 API中经常看到无意识的被实例的类。
**试图通过创建抽象类来强制执行非实例化是行不通的。**该类可以被子类化,子类可以被实例化。此外,它误导用户认为该类是为继承而设计的(条目 19)。不过,有一个简单的方法来确保非实例化。只有当类不包含显式构造方法时,才会生成一个默认构造方法,**因此可以通过包含一个私有构造方法来实现类的非实例化**
```java
// 不可实例化的程序类
class UtilityClass {
// 禁止默认构造函数以实现非实例化
private UtilityClass() {
throw new AssertionError();
}
}
```
因为显式构造方法是私有的,所以在类之外是不可访问的。`AssertionError`异常不是严格要求的,但是它提供了一种保证,以防在类中意外地调用构造方法。它保证类在任何情况下都不会被实例化。这个习惯用法有点违反直觉,好像构造方法就是设计成不能调用的一样。因此,如前面所示,添加注释是种明智的做法。
这种习惯有一个副作用,阻止了类的子类化。所有的构造方法都必须显式或隐式地调用父类构造方法,而子类则没有可访问的父类构造方法来调用。

View File

@ -0,0 +1,38 @@
## 使用依赖注入取代硬连接资源
### 示例
- [Item05Example01.java](CreatingAndDestroyingObjects/src/main/java/com/jueee/item05/Item05Example01.java)
### 说明
许多类依赖于一个或多个底层资源。
例如,拼写检查器依赖于字典。
满足这一需求的简单模式是在创建新实例时将资源传递到构造方法中。
这是依赖项注入dependency injection的一种形式字典是拼写检查器的一个依赖项当它创建时被注入到拼写检查器中。
```java
// 依赖注入提供了灵活性和可测试性
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) { ... }
}
```
虽然我们的拼写检查器的例子只有一个资源(字典),但是依赖项注入可以使用任意数量的资源和任意依赖图。 它保持了不变性(条目 17因此多个客户端可以共享依赖对象假设客户需要相同的底层资源。 依赖注入同样适用于构造方法,静态工厂(条目 1和 builder模式条目 2
该模式的一个有用的变体是将资源工厂传递给构造方法。 工厂是可以重复调用以创建类型实例的对象。 这种工厂体现了工厂方法模式Factory Method pattern )。 Java 8中引入的`Supplier <T>`接口非常适合代表工厂。 在输入上采用`Supplier<T>`的方法通常应该使用有界的通配符类型( bounded wildcard type)(条目 31约束工厂的类型参数以允许客户端传入工厂创建指定类型的任何子类型。 例如下面是一个使用客户端提供的工厂生成tile的方法
`Mosaic create(Supplier<? extends Tile> tileFactory) { ... }`
尽管依赖注入极大地提高了灵活性和可测试性,但它可能使大型项目变得混乱,这些项目通常包含数千个依赖项。使用依赖注入框架(如Dagger、Guice或Spring)可以消除这些混乱。
总之,不要使用单例或静态的实用类来实现一个类,该类依赖于一个或多个底层资源,这些资源的行为会影响类的行为,并且不让类直接创建这些资源。相反,将资源或工厂传递给构造方法(或静态工厂或builder模式)。这种称为依赖注入的实践将极大地增强类的灵活性、可重用性和可测试性。

View File

@ -0,0 +1,13 @@
package com.jueee.item04;
public class Item04Example01 {
}
// 不可实例化的程序类
class UtilityClass {
// 禁止默认构造函数以实现非实例化
private UtilityClass() {
throw new AssertionError();
}
}

View File

@ -0,0 +1,56 @@
package com.jueee.item05;
import java.util.List;
import java.util.Objects;
public class Item05Example01 {
}
class Lexicon{}
class SpellChecker01 {
private final Lexicon dictionary = new Lexicon();
private SpellChecker01() {} // 不可实例化的
public static boolean isValid(String word) {
return false;
}
public static List<String> suggestions(String typo) {
return null;
}
}
class SpellChecker02 {
private final Lexicon dictionary = new Lexicon();
private SpellChecker02() {}
public static SpellChecker02 INSTANCE = new SpellChecker02();
public boolean isValid(String word) {
return false;
}
public List<String> suggestions(String typo) {
return null;
}
}
class SpellChecker03 {
private final Lexicon dictionary;
public SpellChecker03(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {
return false;
}
public List<String> suggestions(String typo) {
return null;
}
}