当我第一次看到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?