写点什么

Spring 项目中用了这种解耦模式,经理对我刮目相看

作者:JAVA旭阳
  • 2023-01-09
    浙江
  • 本文字数:2423 字

    阅读完需:约 8 分钟

前言

不知道大家在项目中有没有遇到过这样的场景,根据传入的类型,调用接口不同的实现类或者说服务,比如根据文件的类型使用 CSV 解析器或者 JSON 解析器,在调用的客户端一般都是用if else去做判断,比如类型等于 JSON,我就用 JSON 解析器,那如果新加一个类型的解析器,是不是调用的客户端还要修改呢?这显然太耦合了,本文就介绍一种方法,服务定位模式Service Locator Pattern来解决,它帮助我们消除紧耦合实现及其依赖性,并提出将服务与其具体类解耦。


欢迎关注个人公众号『JAVA 旭阳』交流沟通

一个例子入门

我们通过一个例子来告诉你如何使用Service Locator Pattern


假设我们有一个从各种来源获取数据的应用程序,我们必须解析不同类型的文件,比如解析 CSV 文件和 JSON 文件。


  1. 定义一个类型的枚举


public enum ContentType {  JSON,  CSV}
复制代码


  1. 定义一个解析的接口


public interface Parser {  List parse(Reader r);}
复制代码


  1. 根据不同的文件类型有不同的实现类


// 解析csv@Componentpublic class CSVParser implements Parser {   @Override  public List parse(Reader r) { .. }}
// 解析json@Componentpublic class JSONParser implements Parser { @Override public List parse(Reader r) { .. }}
复制代码


  1. 最后写一个调用的客户端,通过switch case根据不同的类型调用不同的实现


@Servicepublic class Client {  private Parser csvParser, jsonParser;      @Autowired  public Client(Parser csvParser, Parser jsonParser) {    this.csvParser = csvParser;    this.jsonParser = jsonParser;  }      public List getAll(ContentType contentType) {    ..        switch (contentType) {      case CSV:        return csvParser.parse(reader);      case JSON:        return jsonParser.parse(reader);      ..    }  }  ..}
复制代码


可能大部分人都是像上面一样的方式实现的,也能正常运行,那深入思考下,存在什么问题吗?


现在假如产品经理提出了一个新需求要支持 XML 类型的文件,是不是客户端也要修改代码,需要在switch case中添加新的类型,这就导致客户端和不同的解析器紧密耦合。


那么有什么更好的方法呢?

应用 Service Locator Pattern

没错,那就是用上我们的服务定位模式Service Locator Pattern


  1. 让我们定义我们的服务定位器接口ParserFactory, 它有一个接受内容类型参数并返回Parser的方法。


public interface ParserFactory {  Parser getParser(ContentType contentType);}
复制代码


  1. 我们配置ServiceLocatorFactoryBean使用ParserFactory作为服务定位器接口,ParserFactory这个接口不需要写实现类。


@Configurationpublic class ParserConfig {      @Bean("parserFactory")  public FactoryBean serviceLocatorFactoryBean() {    ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();    // 设置服务定位接口       factoryBean.setServiceLocatorInterface(ParserFactory.class);    return factoryBean;  }
}
复制代码


  1. 设置解析器 Bean 的名称为类型名称,方便服务定位


// 设置bean的名称和类型一致@Component("CSV")public class CSVParser implements Parser { .. }@Component("JSON")public class JSONParser implements Parser { .. }@Component("XML")public class XMLParser implements Parser { .. }
复制代码


  1. 修改枚举, 添加 XML


public enum ContentType {  JSON,  CSV,  XML}
复制代码


  1. 最后用客户端调用,直接根据类型调用对应的解析器,没有了switch case


@Servicepublic class Client {  private ParserFactory parserFactory;  @Autowired  public Client(ParserFactory parserFactory) {    this.parserFactory = parserFactory;  }  public List getAll(ContentType contentType) {    ..    // 关键点,直接根据类型获取    return parserFactory        .getParser(contentType)          .parse(reader);  }  ..}
复制代码


嘿嘿,我们已经成功地实现了我们的目标。现在再加新的类型,我们只要扩展添加新的解析器就行,再也不用修改客户端了,满足开闭原则。


如果你觉得 Bean 的名称直接使用类型怪怪的,这边可以建议你按照下面的方式来。


public enum ContentType {  JSON(TypeConstants.JSON_PARSER),  CSV(TypeConstants.CSV_PARSER),  XML(TypeConstants.XML_PARSER);  private final String parserName;  ContentType(String parserName) {    this.parserName = parserName;  }    @Override  public String toString() {    return this.parserName;  }  public interface TypeConstants {        String CSV_PARSER = "csvParser";    String JSON_PARSER = "jsonParser";    String XML_PARSER = "xmlParser";   }}
@Component(TypeConstants.CSV_PARSER)public class CSVParser implements Parser { .. }@Component(TypeConstants.JSON_PARSER)public class JSONParser implements Parser { .. }@Component(TypeConstants.XML_PARSER)public class XMLParser implements Parser { .. }
复制代码

剖析 Service Locator Pattern

通过前面的例子,想必大家基本知道服务定位器模式如何使用了吧,现在我们深入剖析下。


服务定位器模式消除了客户端对具体实现的依赖。以下引自 Martin Fowler 的文章总结了核心思想: “服务定位器背后的基本思想是拥有一个知道如何获取应用程序可能需要的所有服务的对象。因此,此应用程序的服务定位器将有一个在需要时返回“服务”的方法。”



SpringServiceLocatorFactoryBean实现了 FactoryBean接口,创建了Service Factory服务工厂Bean

总结

我们通过使用服务定位器模式实现了一种扩展 Spring 控制反转的绝妙方法。它帮助我们解决了依赖注入未提供最佳解决方案的用例。也就是说,依赖注入仍然是首选,并且在大多数情况下不应使用服务定位器来替代依赖注入。


欢迎关注个人公众号『JAVA 旭阳』交流沟通

发布于: 刚刚阅读数: 3
用户头像

JAVA旭阳

关注

欢迎关注个人公众号—— JAVA旭阳 2018-07-18 加入

旭阳,希望自己能像初升的太阳一样,对任何事情充满希望~~

评论

发布
暂无评论
Spring项目中用了这种解耦模式,经理对我刮目相看_Java_JAVA旭阳_InfoQ写作社区