ServiceLocator 模式
2024-09-18 08:46:55

当我第一次看到ServiceLocator这个名字的时候以为只是一个常见的类名而已,后来才知道这其实是一种设计模式。
ServiceLocator 充当了一个中介的角色,负责定位和提供服务的实例。它最终目的是为了解决依赖管理的问题。

ServiceLocator 实现

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public interface IService
{
void Execute();
}

public class ServiceA : IService
{
public void Execute()
{
Console.WriteLine("Service A is executing.");
}
}

public class ServiceB : IService
{
public void Execute()
{
Console.WriteLine("Service B is executing.");
}
}

public class ServiceLocator
{
private static Dictionary<string, IService> services = new Dictionary<string, IService>();

public static void RegisterService(string key, IService service)
{
services[key] = service;
}

public static IService GetService(string key)
{
if (services.ContainsKey(key))
{
return services[key];
}

throw new KeyNotFoundException($"Service with key '{key}' is not registered.");
}
}

public class Program
{
public static void Main(string[] args)
{
// 注册服务
ServiceLocator.RegisterService("ServiceA", new ServiceA());
ServiceLocator.RegisterService("ServiceB", new ServiceB());

// 获取并使用服务
IService serviceA = ServiceLocator.GetService("ServiceA");
serviceA.Execute();

IService serviceB = ServiceLocator.GetService("ServiceB");
serviceB.Execute();
}
}

ServiceLocator 就做了一件事:负责提供服务的实例

为什么需要 ServiceLocator?

当我们还是小白,没有任何设计模式的概念时,代码应该是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ServiceA
{
public void Execute()
{
Console.WriteLine("Service A is executing.");
}
}

public class ServiceB
{
public void Execute()
{
var serviceA = new ServiceA();
serviceA.Execute();
Console.WriteLine("Service B is executing.");
}
}

var serviceA = new ServiceB();
serviceA.Execute();

显然,ServiceA 依赖于 ServiceB 的实现,这违反了依赖倒置原则
于是我们引入了接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IServiceA
{
void Execute();
}

public class ServiceA : IServiceA
{
public void Execute()
{
Console.WriteLine("Service A is executing.");
}
}

public class ServiceB
{
public void Execute(IServiceA serviceA)
{
serviceA.Execute();
Console.WriteLine("Service B is executing.");
}
}

var serviceB = new ServiceB();
serviceB.Execute(new ServiceA()); // 注入依赖参数,术语称为 "依赖注入"

现在符合了依赖倒置原则,即:抽象不应该依赖于细节,细节应该依赖于抽象
为什么需要服务定位器?当我们频繁使用依赖注入时,实际上在注入上下文中也产生了依赖:

1
2
3
4
5
6
7
8
public class Foo
{
public void Bar()
{
var serviceB = new ServiceB();
serviceB.Execute(new ServiceA());
}
}

此时虽然 ServiceB 不依赖 ServiceA,但是 Foo 却和 ServiceA、ServiceB 耦合了。
此时就需要 ServiceLocator 登场了。通过使用 ServiceLocator,应用程序的各个部分可以统一通过服务定位器获取它们所需的服务,而无需直接依赖于具体的实现类。这有助于减少类之间的直接依赖,提高灵活性和可维护性。
采用 ServiceLocator 后的代码:

1
2
3
4
5
6
7
8
public class Foo
{
public void Bar()
{
var serviceB = ServiceLocator.GetService<IServiceB>();
serviceB.Execute(ServiceLocator.GetService<IServiceA>());
}
}

Common Service Locator

CommonServiceLocator 是一个服务定位器的通用实现。相较于普通的服务定位器,CommonServiceLocator的区别在于它是全局唯一的服务定位器,通过设置全局的 ServiceLocator 提供者,可以在整个应用程序中使用同一个 ServiceLocator 来解析服务,而不需要每个地方都创建一个新的服务定位器实例。

Unity 容器为例:

1
2
3
4
5
6
7
8
9
10
11
using CommonServiceLocator;
using Unity;
using Unity.ServiceLocation;

var container = new UnityContainer();
container.RegisterType<School>();

var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);

var obj = ServiceLocator.Current.GetInstance<School>();

服务定位器会隐藏具体的容器,使得调用者依赖定位器而不是容器。
除了 Unity 外,一些主流的容器都有对应的 CommonServiceLocator 包装。

参考

Service Locator Pattern in C#
Common Service Locator
Service Locator is an Anti-Pattern
Dependency Injection vs Service Location
What’s the difference between the Dependency Injection and Service Locator patterns?