在SpringBoot中实现策略模式

哥们看看码农 2024-07-31 01:19:16

策略模式是一种行为模式,它使我们能够在运行时选择算法的行为。这种模式允许我们定义一组算法,将它们放在不同的类中,并使它们可以互换[1]。

这只是一个定义,但让我们通过了解我们试图解决的问题来更好地理解它。

问题

假设你正在开发一个名为文件解析器的功能。你需要编写一个 API,用户可以上传文件,我们的系统应该能够从中提取数据并将其持久化到数据库中。目前我们被要求支持 CSV、JSON 和 XML 文件。我们的直接解决方案可能如下所示。

@Servicepublic FileParserService { public void parse(File file, String fileType) { if (Objects.equals(fileType, "CSV")) { // TODO : a huge implementation to parse CSV file and persist data in db } else if (Objects.equals(fileType, "JSON")) { // TODO : a huge implementation to parse JSON file and persist data in db } else if (Objects.equals(fileType, "XML")) { // TODO : a huge implementation to parse XML file and persist data in db } else { throw new IllegalArgumentException("Unsupported file type"); } } }

从业务角度来看,现在一切都很好,但当我们将来想要支持更多文件类型时,事情就会开始变得糟糕。我们开始添加多个 else if 块,类的规模会迅速增长,最终变得难以维护。对文件解析器实现的任何更改都会影响整个类,从而增加在已经正常工作的功能中引入错误的机会。

不仅如此,还有另一个问题。假设我们现在需要额外支持 sqlite 和 parquet 文件类型。两个开发者会介入并开始在同一个庞大的类上工作。他们很可能会遇到合并冲突,这不仅对任何开发者来说都很烦人,而且解决冲突也很耗时。最重要的是,即使在冲突解决后,对整个功能正常工作的信心也会降低。

解决方案

这就是策略设计模式介入并拯救我们的地方。我们将所有文件解析器实现移动到单独的类中,称为策略。在当前类中,我们将根据文件类型动态获取适当的实现并执行策略。

以下是一个 UML 图,提供我们即将实现的设计模式的高级概述。

现在,让我们深入代码。

我们需要一个类来维护支持的不同文件类型。稍后我们将使用它来创建具有自定义名称的 Spring Bean(即策略)。

public FileType { public static final String CSV = "CSV"; public static final String XML = "XML"; public static final String JSON = "JSON";}

创建一个文件解析器的接口

public interface FileParser { void parse(File file);}

既然我们已经创建了一个接口,让我们为不同的文件类型创建不同的实现,即策略

@Service(FileType.CSV)public CsvFileParser implements FileParser { @Override public void parse(File file) { // TODO : impl to parse csv file } }@Service(FileType.JSON)public JsonFileParser implements FileParser { @Override public void parse(File file) { // TODO : impl to parse json file } }@Service(FileType.XML)public XmlFileParser implements FileParser { @Override public void parse(File file) { // TODO : impl to parse xml file } }

注意,我们已经为上述 Bean 提供了自定义名称,这将帮助我们将这三个 Bean 注入到我们需要的类中。

现在我们需要找到一种方法,在运行时根据文件类型选择上述实现之一。

让我们创建一个 FileParserFactory 类。这个类负责在给定文件类型时决定选择哪个实现。我们将利用 Spring Boot 强大的依赖注入功能在运行时获取适当的策略。(有关更多详细信息,请参阅以下代码块中的注释或 [2])

@Component@RequiredArgsConstructorpublic FileParserFactory { /** * Spring boot's dependency injection feature will construct this map for us * and include all implementations available in the map with the key as the bean name * Logically, the map will look something like below * { * "CSV": CsvFileParser, * "XML": XmlFileParser, * "JSON": JsonFileParser * } */ private final Map<String, FileParser> fileParsers; /** * Return's the appropriate FileParser impl given a file type * @param fileType one of the file types mentioned in FileType * @return FileParser */ public FileParser get(String fileType) { FileParser fileParser = fileParsers.get(fileType); if (Objects.isNull(fileParser)) { throw new IllegalArgumentException("Unsupported file type"); } return fileParser; }}

现在,让我们对 FileParserService 进行更改。我们将使用 FileParserFactory 根据 fileType 获取适当的 FileParser 并调用 parse 方法。

@Service@RequiredArgsConstructorpublic FileParserService { private final FileParserFactory fileParserFactory; public void parse(File file, String fileType) { FileParser fileParser = fileParserFactory.get(fileType); fileParser.parse(file); } }

就是这样。我们完成了!

结论

如果我们需要支持更多文件类型,我们只需创建像 SqliteFileParser 和 ParquetFileParser 这样的新类,这些类实现了 FileParser 接口。因此,多个开发者实现这些新文件解析器时将避免任何合并冲突。

现有的文件解析器保持不变,从而减少了破坏现有功能的机会。

此外,我们的代码现在符合 SOLID 原则,特别是我们喜爱的开闭原则。通过将文件解析实现封装到单独的类中,我们可以在不修改现有代码的情况下扩展系统的新解析策略。这使得我们的系统更能适应未来的需求,更易于维护。

参考资料https://refactoring.guru/design-patterns/strategyhttps://docs. spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html

欢迎SpringForAll社区(spring4all.com),专注分享关于Spring的一切!

0 阅读:0

哥们看看码农

简介:感谢大家的关注