Using a Service Locator
The key benefit of a Dependency Injector is that it removes the dependency that the MovieLister
class has on the concrete MovieFinder
implementation. This allows me to give listers to friends
and for them to plug in a suitable implementation for their own environment. Injection isn't
the only way to break this dependency, another is to use a service locator.
使用依赖注入的主要好处就是能够消除MovieLister对具体实现MovieFinder的依赖。这允许我把Movielister
给朋友并让他们根据自身环境情况注入相应的实现。注入并不是解除依赖关系的唯一方法,还有一种方法也可以
解除依赖关系,那就是使用服务定位器。
The basic idea behind a service locator is to have an object that knows how to get hold of
all of the services that an application might need. So a service locator for this application
would have a method that returns a movie finder when one is needed. Of course this just shifts
the burden a tad, we still have to get the locator into the lister, resulting in the dependencies
of Figure。
服务定位器的最基本的思想就是有一个对象定位器知晓如何控制应用程序需要的所有服务。所以以上例子中
提到的应用程序的一个服务定位器在需要的时候将会使用某个方法返回一个movie finder的实例。当然这仅仅是
转移一些负担而已,我们仍旧必须取得进入lister的服务定位器,结果就呈现以下的依赖关系。
In this case I'll use the ServiceLocator as a singleton Registry. The lister can then use that to
get the finder when it's instantiated.
在下面这个例子中,我将使用服务定位器作为单例注册器。MovieLister能够使用该服务定位器在初始化时得到
MovieFinder的实例。
class MovieLister...
MovieFinder finder = ServiceLocator.movieFinder();
class ServiceLocator...
public static MovieFinder movieFinder() {
return soleInstance.movieFinder;
}
private static ServiceLocator soleInstance;
private MovieFinder movieFinder;
As with the injection approach, we have to configure the service locator. Here I'm doing it in code, but it's not hard
to use a mechanism that would read the appropriate data from a configuration file.
就像注入方法一样,我们必须配置服务定位器。下面我用代码来完成这个功能,但是使用从配置文件读取相应数据的机制也并不困难。
class Tester...
private void configure() {
ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));
}
class ServiceLocator...public static void load(ServiceLocator arg) {soleInstance = arg;}public ServiceLocator(MovieFinder movieFinder) {this.movieFinder = movieFinder;}Here's the test code.
下面就是测试代码。
class Tester...
public void testSimple() {configure();MovieLister lister = new MovieLister();Movie[] movies = lister.moviesDirectedBy("Sergio Leone");assertEquals("Once Upon a Time in the West", movies[0].getTitle());}I've often heard the complaint that these kinds of service locators are a bad thing because they aren't testable
because you can't substitute implementations for them. Certainly you can design them badly to get into this kind of
trouble, but you don't have to. In this case the service locator instance is just a simple data holder. I can easily
create the locator with test implementations of my services.
我经常听到一些抱怨,说这些服务定位器如何如何不好,因为找不到替代实现而不可测试。当然,你的设计如果很差,自然会被这
类问题纠缠,但是你可以选择好的设计。在本例中服务定位器只是如此简单的数据容器,我很容易使用服务的测试实现来创建我的定位器。
For a more sophisticated locator I can subclass service locator and pass that subclass into the registry's class variable.
I can change the static methods to call a method on the instance rather accessing instance variables directly. I can provide
thread specific locators by using thread specific storage. All of this can be done without changing clients of service locator.
对于更高级一点的定位器,我可以用子类继承并且传递子类给注册器的类变量(实现一个ServiceLocator的子类并且在初始化时传递给
soleInstance)。我可以修改静态方法来调用ServiceLocator实例的一个方法而不是直接访问实例变量。(不是直接访问
soleInstance.movieFinder方法)。我还可以提供线程相关的存储机制来提供线程相关的定位器。所有的这些都无需改变客户端的服务定位器。
A way to think of this is that service locator is a registry not a singleton. A singleton provides a simple way of
implementing a registry, but that implementation decision is easily changed.
一种改进的办法是:服务定位器仍然是注册器而非singleton.singleton提供了一种简单实现注册机的办法,但是那种实现很容易被改变。
Using a Segregated Interface for the Locator
对服务定位器采用隔离接口
One of the issues with the simple approach above, is that the MovieLister is dependent on the full service locator class,
even though it only uses one service. We can reduce this by using a segregated interface. That way, instead of using the
full service locator interface, the lister can declare just the bit of interface it needs.
以上所演示的简单方法存在一个问题:MovieLister完全依赖service locator类,尽管它只是使用了一种服务。我们可以采用隔离接口来减少
这种依赖。那样的话,MovieLister可以只是声明它需要的接口,而不是使用整个service locator接口。
In this situation the provider of the lister would also provide a locator interface which it needs to get hold of the finder.
在这种情况之下,MovieLister的提供者也将提供一个locator接口,MovieLister需要这个locator接口来控制MovieFinder.
public interface MovieFinderLocator {public MovieFinder movieFinder();The locator then needs to implement this interface to provide access to a finder.
服务定位器需要实现这个接口来提供对MovieFinder的访问。
MovieFinderLocator locator = ServiceLocator.locator();(如此隔离开不同服务的人使用的接口,叫做接口隔离,以免接口污染)MovieFinder finder = locator.movieFinder();public static ServiceLocator locator() {return soleInstance;}public MovieFinder movieFinder() {return movieFinder;}private static ServiceLocator soleInstance;private MovieFinder movieFinder;You'll notice that since we want to use an interface, we can't just access the services through static methods any
more. We have to use the class to get a locator instance and then use that to get what we need.
你会发现因为我们使用了一个接口,我们不能再仅仅通过静态方法来访问服务,我们必须使用类来得到一个定位器实例,让后通过该
实例得到我们要得到的服务。
A Dynamic Service Locator
动态服务定位器The above example was static, in that the service locator class has methods for each of the services that you need.
This isn't the only way of doing it, you can also make a dynamic service locator that allows you to stash any service
you need into it and make your choices at runtime.
以上是静态定位器的例子,对于你所需要的每一项服务,service locator类都有对应的方法.这并不是实现服务定位器的唯一方式,你也
可以使用动态定位器,动态定位器允许你注册任何你所需要的服务,并在运行期决定使用哪一项服务。
In this case, the service locator uses a map instead of fields for each of the services, and provides generic methods
to get and load services.
本例中,服务定位器使用map来代替每一个服务字段,并且使用通用的方法来得到和定位服务。
class ServiceLocator...private static ServiceLocator soleInstance;public static void load(ServiceLocator arg) {soleInstance = arg;}private Map services = new HashMap();public static Object getService(String key){return soleInstance.services.get(key);}public void loadService (String key, Object service) {services.put(key, service);}Configuring involves loading a service with an appropriate key.
同样需要对服务定位器进行配置,将服务对象和适当的关键字加载到定位器中。
class Tester...private void configure() {ServiceLocator locator = new ServiceLocator();locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));ServiceLocator.load(locator);}I use the service by using the same key string.
我使用与服务对象类名称相同的字符串作为关键字。
class MovieLister...MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");On the whole I dislike this approach. Although it's certainly flexible, it's not very explicit. The only way I can
find out how to reach a service is through textual keys. I prefer explicit methods because it's easier to find where
they are by looking at the interface definitions.
大体上我并不喜欢这种方法。尽管他相当有弹性,但是它的使用方法不够直观明朗。我要定位一个服务只有通过文本形式的关键字。
我更喜欢直观的方法,因为通过查看接口定义就很容易定位服务。
Using both a locator and injection with Avalon
Dependency injection and a service locator aren't necessarily mutually exclusive concepts. A good example of using
both together is the Avalon framework. Avalon uses a service locator, but uses injection to tell components where
to find the locator.
依赖注入和服务定位并非相互排斥的概念。两者全被使用的一个好的典范是Avalon 框架,Avalon使用了服务定位器,但是也使用了注入
来告诉部件如何找到定位器。
Berin Loritsch sent me this simple version of my running example using Avalon.
Berin Loritsch发了一个前面例子的Avalon的简单版本给我。
public class MyMovieLister implements MovieLister, Serviceable {private MovieFinder finder; public void service( ServiceManager manager ) throws ServiceException {finder = (MovieFinder)manager.lookup("finder");}The service method is an example of interface injection, allowing the container to inject a service manager into MyMovieLister.
The service manager is an example of a service locator. In this example the lister doesn't store the manager in a field,
instead it immediately uses it to lookup the finder, which it does store.
service方法就是接口注入的一个例子。允许容器把服务管理器注入MyMovieLister.服务管理器是服务定位器的一个例子。在这个例子中,lister
并不以字段的形式存储manager,而是使用它立即找到MovieFinder,并以字段的形式存储MovieFinder.