前言不知道大家在项目中遇到过这样的场景,根据输入的类型,调用不同的实现类或者服务接口的,例如,根据文件的类型使用CSV解析器或JSON解析器。调用客户端一般用ifelse来判断,比如type等于json,我就用json解析器。如果增加了一种新类型的解析器,调用客户端是否需要修改?这显然太耦合了。本文介绍一种方法ServiceLocatorPattern来解决。它帮助我们消除紧密耦合的实现及其依赖性,并建议将服务与其具体类解耦。一个文件解析器的例子让我们用一个例子来告诉你如何使用服务定位器模式。假设我们有一个从各种来源获取数据的应用程序,我们要解析不同类型的文件,比如解析CSV文件和JSON文件。定义一个枚举类型publicenumContentType{JSON,CSV}定义一个解析接口publicinterfaceParser{Listparse(Readerr);}根据不同的文件类型有不同的实现类//parsecsv@ComponentpublicclassCSVParserimplementsParser{@OverridepublicListparse(Readerr){..}}//解析json@ComponentpublicclassJSONParserimplementsParser{@OverridepublicListparse(Readerr){..}}最后写一个调用客户端,通过switchcase根据不同的类型调用不同的实现@ServicepublicclassClient{privateParsercsvParser,jsonParser;@AutowiredpublicClient(ParsercsvParser,ParserjsonParser){this.csvParser=csvParser;this.jsonParser=jsonParser;}publicListgetAll(ContentTypecontentType){..switch(contentType){caseCSV:returncsvParser.parse(reader);案例JSON:returnjsonParser.parse(reader);..}}..}可能大部分人都像上面这样也能正常运行,那么大家仔细想想,是不是有什么问题呢?现在如果产品经理提出新的要求支持XML类型的文件,客户端是否需要修改代码,在switchcase中增加一个新的类型,导致客户端和不同解析器之间的紧耦合。那么有什么更好的方法呢?应用ServiceLocatorPattern是对的,就是用我们的ServiceLocatorPattern。让我们定义我们的服务定位器接口ParserFactory,它有一个方法接受内容类型参数并返回一个解析器。publicinterfaceParserFactory{ParsergetParser(ContentTypecontentType);}我们将ServiceLocatorFactoryBean配置为使用ParserFactory作为服务定位器接口。ParserFactory不需要为此接口编写实现类。@ConfigurationpublicclassParserConfig{@Bean("parserFactory")publicFactoryBeanserviceLocatorFactoryBean(){ServiceLocatorFactoryBeanfactoryBean=newServiceLocatorFactoryBean();//设置服务定位接口factoryBean.setServiceLocatorInterface(ParserFactory.class);返回工厂Bean;}}设置parserbean的名字就是类型名,方便服务定位。//设置bean的名称和类型要一致。@Component("CSV")publicclassCSVParserimplementsParser{..}@Component("JSON")publicclassJSONParserimplementsParser{..}@Component("XML")publicclassXMLParserimplementsParser{..}修改枚举,添加XMLpublicenumContentType{JSON,CSV,XML}最后使用客户端根据类型直接调用对应的解析器,不用switchcase@ServicepublicclassClient{privateParserFactoryparserFactory;@AutowiredpublicClient(ParserFactoryparserFactory){this.parserFactory=parserFactory;}publicListgetAll(ContentTypecontentType){..//关键点,根据type直接get返回parserFactory.getParser(contentType).parse(reader);}..}嘿嘿,我们已经成功地达到了我们的目的。现在要增加新的类型,只需要扩充和增加新的解析器即可,不需要再修改客户端,满足开闭原则。如果你觉得直接使用类型作为bean的名字很奇怪,我可以建议你按照下面的方式来做。publicenumContentType{JSON(TypeConstants.JSON_PARSER),CSV(TypeConstants.CSV_PARSER),XML(TypeConstants.XML_PARSER);私有最终字符串解析器名称;ContentType(StringparserName){this.parserName=parserName;}@OverridepublicStringtoString(){returnthis.parserName;}publicinterfaceTypeConstants{StringCSV_PARSER="csvParser";字符串JSON_PARSER="jsonParser";字符串XML_PARSER="xmlParser";}}@Component(TypeConstants.CSV_PARSER)公共类CSVParser实现解析器{..}@Component(TypeConstants.JSON_PARSER)公共类JSONParser实现解析器{..}@Component(TypeConstants.XML_PARSER)公共类XMLParser实现解析器{..}ServiceLocator模式浅析通过前面的例子,大家想必对ServiceLocator模式有了基本的了解和使用,下面我们来深入分析一下。服务定位器模式消除了客户端对具体实现的依赖。MartinFowler的以下引述总结了核心思想:“服务定位器背后的基本思想是拥有一个知道如何获取应用程序可能需要的所有服务的对象。因此,此应用程序的服务定位器将有一个在需要时返回“服务”的方法。》Spring的ServiceLocatorFactoryBean实现了FactoryBean接口,创建了一个ServiceFactory服务工厂bean。总而言之,我们实现了一个很好的方式,通过使用服务定位器模式来扩展Spring的控制反转。它帮助我们解决了依赖注入的问题。用例提供最好的解决方案。也就是说,依赖注入仍然是首选,并且在大多数情况下不应该被用作依赖注入的替代品。
