• No results found

Limitations of string keys

This chapter covers: ■ Using dependency injection

2.4 Identifying dependencies for injection

2.4.2 Limitations of string keys

String keys are inherently problematic when one factors human error into the equa- tion. We have all been there. Take the possible ways to spell a common name, for example:

Alan

AllanAlainAllen

Allun (an Irish variant)

This is a short name (two syllables) and it does not incur the distractions of case and mul- tiple-word capitalization.9 Yet we can barely agree on a spelling even in the English- speaking world. Mistyping and misreading exacerbates the problem. It is not a stretch to imagine how things can get out of hand very quickly with a large number of services identified by many similar-looking (and sounding) keys. A misspelled key that maps to no valid service can be difficult to detect until well into the runtime of the program.

Furthermore, if you are working with a statically typed language like Java, this is a poor sacrifice to have to make. These languages are supposed to offer guarantees around type validity at compile time. Ideally, one should not have to wait until run- time to detect problems in key bindings.10

Sometimes tools like IDEs or build agents can help do this extra level of checking for you, during or before compilation. IntelliJ IDEA11 provides plug-ins for Spring for just this purpose. Naturally, it is difficult for any tool to guarantee the correct resolution of dependencies into dependents without bootstrapping the injector and walking across every bound object (or just about every one). As a result, this problem can arise often in injectors that use string keys.

9 A phrase or set of words in a key is often delimited by capitalizing each first letter. For example, “Technicolor

dream coat” would be written as TechnicolorDreamCoat or technicolorDreamCoat, which are two forms of the CamelCase convention.

10Do not confuse static and dynamic typing with strong and weak typing, or indeed with duck typing.

11IntelliJ IDEA is an advanced Java IDE developed by JetBrains. It is usually at the forefront of innovation in

developer productivity. Find out more at http://www.jetbrains.com.

Static and dynamic typing10

Types are specific classes of data in a programming language. For example, the num- ber 32 is of an Integer type. Types may also be user defined, such as Car or Song. Before operations on data can occur, the data’s particular type must be determined. This is known as type resolution. A dynamically typed language resolves types when an operation is performed, whereas a statically typed language resolves types when an operation is defined (typically, but not necessarily, at compile time).

In your injector configuration there is no way to determine the type of a binding if all you have is a string key. Without starting up the injector and inspecting the spe- cific instance for a key, it is hard to determine what type the key is bound to. Take the following example of a game console (the Nintendo Wii) and a game to be played on it:

<beans>

<bean id="nintendo.wii" class="com.nintendo.Wii"> <constructor-arg ref="game.HalfLife"/>

</bean>

<bean id="game.HalfLife" class="com.valve.HalfLifeGame"/> </beans>

Here our keys appear reasonably well chosen: they are unique, arbitrary, and explicit (and they use namespaces). I’ve followed every tenet of good key behavior but some- thing is horribly wrong with this injector configuration. The program crashes when I try to run it. To understand why, delve into the code behind these bindings:

package com.nintendo; public class Wii { private WiiGame game;

public Wii(WiiGame game) { this.game = game;

}

public void play() { .. } }

The Nintendo game system takes an argument of type WiiGame via its constructor. This dependency represents a game I want to play (I enjoy Half-Life so let’s go with that) and is loaded by the game system when play() is called. This is the game Half-Life: package com.valve;

public class HalfLife implements PCGame { public void load() { .. }

}

Oh, no! Half-Life is a game for the PC,

not the Wii! It is not an instance of Wii-

Game and therefore cannot be wired into Wii’s constructor (see figure 2.14).

In Java, executing this program and requesting an instance by key nintendo. wii will result in an UnsatisfiedDepen- dencyException being thrown. This indi- cates that the configured binding is of the wrong type for Wii’s dependency, and it cannot be converted to the right type.

Game is wired via constructor Wii PCGame WiiGame HalfLife << interface >> - game << interface >>

Figure 2.14 Wii and game HalfLife, which implements the wrong interface, PCGame

More significant for us, however, is the fact that what appeared to be a perfectly well- written injector configuration turned out to be fatally flawed. Worse, there was no way to detect this until the offending object graph was constructed and used. This highlights a serious limitation of string keys.

Once the problem is identified, we can fix it by selecting the correct implementa- tion of Half-Life. Listing 2.14 shows this fix, illustrated in figure 2.15.

<beans>

<bean id="nintendo.wii" class="com.nintendo.Wii"> <constructor-arg ref="game.HalfLife"/>

</bean>

<bean id="game.HalfLife" class="com.valve.wii.HalfLife"/> </beans>

This time, the game is of the appropriate type and everything works as expected. package com.valve.wii;

public class HalfLife implements WiiGame { public void load() { .. }

}

In large applications with many hundreds of object graphs, string identifiers incur many of these problems. No matter what language or injector you decide on, poorly chosen keys are a recipe for disas- ter. Oh, and use namespaces. Seriously!