为更好理解依赖注入模式,特意去了解服务定位器模式,今日留下一文笔记,以助巩固。
Martin Fowler提出的服务器定位器概念实际上是一种反模式,简单而言,服务器隐藏了类之间依赖关系,从而增加了维护难度。
服务定位器实现代码如下:
public static class Locator { //定位器服务集合 private readonly static Dictionary<Type, Func<object>> services = new Dictionary<Type, Func<object>>(); //往定位器的服务塞东西 -注册服务 public static void Register<T>(Func<T> resolver) { Locator.services[typeof(T)] = () => resolver(); } //根据参数从定位器的服务里拿东西 -解析 public static T Resolve<T>() { return (T)Locator.services[typeof(T)](); } //清空定位器的服务 public static void Reset() { Locator.services.Clear(); } }
真正的服务定位器代码实现比这个要复杂,但是这个实现已经抓住了重点。(不要过于纠结于此,先理解是关键)。
现在创建订单处理对象,对象里使用上面的静态的服务定位器,代码如下:
public class OrderProcessor : IOrderProcessor { public void Process(Order order) { //从定位器里拿出注册该接口的服务 (定位器不一定有注册了该接口的服务,运行时可能抛异常,编译时是不能知道有没有这个服务的。 var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { //从定位器里拿出注册该接口的服务 (定位器不一定有注册了该接口的服务,运行时可能抛异常,编译时是不能知道有没有这个服务的。 var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
现在假设我们是上面订单的消费者,如何处理订单的接口都外包给第三方做了,我们并不清楚处理订单的源码。我们创建订单(通过IDE的代码提示我们清楚有这么个订单对象),代码如下:
var dealOrder = new OrderProcessor();
创建订单,调用处理订单方法,代码如下:
var order = new Order(); var dealOrder = new OrderProcessor(); sut.Process(order);
运行这段代码(即运行时)抛出了一个KeyNotFoundException(主键找不到异常),因为IOrderValidator从来没有在定位器上注册过,通过仔细阅读源代码或查阅文档,又或问第三方技术支持者,我们可能最终会发现,在运行代码之前,我们需要用服务定位器给IOrderValidator接口注册一个服务,该服务是一个静态类。
烦躁,这种开发体验并不是那么友好!之前都没有告诉要注册这个服务,等我执行后才告诉我去补工作。气呀!
更令人郁闷的是,如果我运行成功了,不抛异常,我所调用的服务会不会在别的地方(我不知道的地方,毕竟大部分公司都是团队开发项目)注册的,而这个服务又不是我真的想要的。
上面的服务定位器里多处用到static关键字:静态类、静态成员,这个东西不能随便用。 static关键字学习 。
服务定位器使用静态类会产生如下弊端
1、因为类是静态类,所以到处的订单处理对象实例都是共用一个服务定位器对象。到处都可以注册服务,多危险呀。当前对象会不会一不小心用到了不该用的服务呢?
2、如果想扩展服务定位器的成员方法,例如代码如下:
public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { //多用了个服务 var collector = Locator.Resolve<IOrderCollector>(); collector.Collect(order); var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } }
会不会在扩展'增加使用多一个IOrderCollector的服务'之前,就有别处给这个定位器注册这个服务,而这个服务又不是某个订单处理调用Process方法想要的结果,难道我还要加个修改已经注册了服务的方法,那我修改某个注册的服务,就不会别的处理对象实例产生影响吗?岂不糟糕透了!
如果不使用静态了,类不用静态修饰,成员不用静态修饰。把服务定位器成为一个注入点(依赖注入),结果会怎样呢?
参考文章: http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern