Item 5: Prefer dependency injection to hardwiring resources(依赖注入优于硬连接资源)

Many classes depend on one or more underlying(adj.潜在的,根本的) 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):

许多类依赖于一个或多个底层资源。例如,拼写检查程序依赖于字典。常见做法是,将这种类实现为静态实用工具类(Item-4):

// Inappropriate use of static utility - inflexible & untestable!
public class SpellChecker {
    private static final Lexicon dictionary = ...;
    private SpellChecker() {} // Noninstantiable
    public 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):

类似地,我们也经常看到它们的单例实现(Item-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 支持多个字典:首先取消 dictionary 字段的 final 修饰,并在现有的拼写检查器中添加更改 dictionary 的方法。但是在并发环境中这种做法是笨拙的、容易出错的和不可行的。静态实用工具类和单例不适用于由底层资源参数化的类。

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),每个实例都使用客户端需要的资源(在我们的示例中是 dictionary)。满足此要求的一个简单模式是在创建新实例时将资源传递给构造函数。 这是依赖注入的一种形式:字典是拼写检查器的依赖项,在创建它时被注入到拼写检查器中。

// 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(adj.任意的) 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).

依赖注入模式非常简单,许多程序员在不知道其名称的情况下使用了多年。虽然拼写检查器示例只有一个资源(字典),但是依赖注入可以处理任意数量的资源和任意依赖路径。它保持了不可变性(Item-17),因此多个客户端可以共享依赖对象(假设客户端需要相同的底层资源)。依赖注入同样适用于构造函数、静态工厂(Item-1)和构建器(Item-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<T> interface, introduced in Java 8, is perfect for representing factories. Methods that take a Supplier<T> 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> 的方法通常应该使用有界通配符类型(Item-31)来约束工厂的类型参数,以允许客户端传入创建指定类型的任何子类型的工厂。例如,这里有一个生产瓷砖方法,每块瓷砖都使用客户提供的工厂来制作马赛克:

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

Although dependency injection greatly improves flexibility and testability, it can clutter up(使杂乱) large projects, which typically(adv.通常) 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(n.手册;adj.手工的) dependency injection are trivially(adv.繁琐地) adapted for(适用于) use by these frameworks.

尽管依赖注入极大地提高了灵活性和可测试性,但它可能会使大型项目变得混乱,这些项目通常包含数千个依赖项。通过使用依赖注入框架(如 Dagger、Guice 或 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.

总之,不要使用单例或静态实用工具类来实现依赖于一个或多个底层资源的类,这些资源的行为会影响类的行为,也不要让类直接创建这些资源。相反,将创建它们的资源或工厂传递给构造函数(或静态工厂或构建器)。这种操作称为依赖注入,它将大大增强类的灵活性、可重用性和可测试性。


Back to contents of the chapter(返回章节目录)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,336评论 0 10
  • 现在是凌晨两点半左右,好久没有熬到这么晚。无数的琐事涌上头来,凌乱无序却又那么清晰的警示着我还剩下不多的时...
    _晨曦_阅读 210评论 0 1
  • 在风和日丽的冬天,太阳暖暖升起的早晨,参加一场特殊的婚礼,放空的时间,是错过的节点,有遗憾,却并没有悔恨,只是生...
    夜里飞行的猫阅读 338评论 1 0
  • 春水初生 春林初盛 春风十里 不如你。
    吴柯南阅读 238评论 0 0