【设计模式】服务定位器模式
前言
服务定位器模式(Service Locator Pattern)是控制反转原理的实现方式之一。本文详细介绍该模式,并提供了 UML 图和示例 Java 代码。
服务定位器模式
服务定位器模式实现了按需返回服务实例。在该模式中,应用所有需要的服务都会被注册到服务定位器中,并通过 ID 唯一标识。应用需要哪个服务,用这个 ID 就能从服务定位器中得到这个服务的实例。服务定位器模式解耦了服务调用者和具体的服务实现。该模式的 UML 类图可以表示如下:
服务定位器模式包含如下组件:
- Client:服务调用者,向服务定位器发出服务调用请求
- Service Locator:客户端/服务端间通信入口,从 Cache 中返回服务
- Cache:缓存、复用服务
- Initializer:创建、注册服务到 Cache 中
- Service:服务具体实现
Java 示例
让我们看一个具体的例子来理解服务定位器模式,准备代码如下:
// Java program to
// illustrate Service Design Service
// Locator Pattern
import java.util.ArrayList;
import java.util.List;
首先,定义服务 Service 接口以及两个具体服务实现:
// Service interface
// for getting name and
// Executing it.
interface Service {
public String getName();
public void execute();
}
// Service one implementing Locator
class ServiceOne implements Service {
public void execute()
{
System.out.println("Executing ServiceOne");
}
@Override
public String getName()
{
return "ServiceOne";
}
}
// Service two implementing Locator
class ServiceTwo implements Service {
public void execute()
{
System.out.println("Executing ServiceTwo");
}
@Override
public String getName()
{
return "ServiceTwo";
}
}
接着定义 Service Locator 类,该服务器类实现了一个服务 Cache 以及一个服务实例返回函数 getService。getService 函数会优先返回缓存的服务实例,如果没有,则通过 Initializer 创建。
// Locator class
class ServiceLocator {
private static Cache cache;
static
{
cache = new Cache();
}
public static Service getService(String name)
{
Service service = cache.getService(name);
if (service != null) {
return service;
}
InitialContext context = new InitialContext();
Service ServiceOne = (Service)context.lookup(name);
cache.addService(ServiceOne);
return ServiceOne;
}
}
上面提到的 Cache 类和 Initializer 类具体实现如下:
class Cache {
private List<Service> services;
public Cache()
{
services = new ArrayList<Service>();
}
public Service getService(String serviceName)
{
for (Service service : services) {
if (service.getName().equalsIgnoreCase(serviceName)) {
System.out.println("Returning cached "
+ serviceName + " object");
return service;
}
}
return null;
}
public void addService(Service newService)
{
boolean exists = false;
for (Service service : services) {
if (service.getName().equalsIgnoreCase(newService.getName())) {
exists = true;
}
}
if (!exists) {
services.add(newService);
}
}
}
// Checking the context
// for ServiceOne and ServiceTwo
class InitialContext {
public Object lookup(String name)
{
if (name.equalsIgnoreCase("ServiceOne")) {
System.out.println("Creating a new ServiceOne object");
return new ServiceOne();
}
else if (name.equalsIgnoreCase("ServiceTwo")) {
System.out.println("Creating a new ServiceTwo object");
return new ServiceTwo();
}
return null;
}
}
最后是 Client 类,测试代码效果:
// Client class
class ServiceConsumer {
public static void main(String[] args)
{
Service service = ServiceLocator.getService("ServiceOne");
service.execute();
service = ServiceLocator.getService("ServiceTwo");
service.execute();
service = ServiceLocator.getService("ServiceOne");
service.execute();
service = ServiceLocator.getService("ServiceTwo");
service.execute();
}
}
输出结果:
Creating a new ServiceOne object
Executing ServiceOne
Creating a new ServiceTwo object
Executing ServiceTwo
Returning cached ServiceOne object
Executing ServiceOne
Returning cached ServiceTwo object
Executing ServiceTwo
总结
与依赖注入的区别
两种模式都是控制反转原理的实现,即对象不应该知道如何构造其依赖项。不同点在于,对于服务定位器模式,Client 仍然需要对依赖项的创建负责,虽然借助了 Service Locator 来完成。
优缺点
优点:
- 应用可以在运行时,选择添加或移除依赖服务
- 应用和服务间松耦合,唯一的连接仅仅在于服务定位器的服务注册
缺点:
- 服务定位器的服务注册使代码更加复杂,不得不维护比较多的服务唯一标识 ID
- 如果依赖有错误,只能在运行时报错,不能在编译时发现
- 由于应用仍要负责创建依赖项,因此不能 mock 依赖,不方便写测试代码
参考资料
[1] 30分钟学会UML类图
[2] Baeldung: Service Locator Pattern
[3] GeeksforGeeks: Service Locator Pattern