【问题标题】:Java SE - Clever way to implement "plug and play" for different library modulesJava SE - 为不同的库模块实现“即插即用”的巧妙方法
【发布时间】:2015-12-23 19:03:26
【问题描述】:

我正在尝试做一些聪明的事情。我正在创建一个天气应用程序,我们可以在其中用另一个天气 API 替换天气 API,而不会影响代码库。所以我从一个包含多个模块的 Maven 项目开始。

我有一个包含 Interface 类和 Base 类的 Base 模块。 Interface 类包含对 API 的调用(所有调用都是相似的,如果不准确的话),Base 类包含 API 的属性(同样,所有属性都是相似的,如果不准确的话)。

我们正在测试的两个天气 API 中的每一个都有一个模块,并计划随着应用程序的发展为新的天气 API 创建更多模块。

最后,我创建了一个 Core 模块(包括 main)来实现我要测试的天气 API 的特定模块类。

现在,我知道最简单的方法是使用 switch 语句和枚举。但我想知道是否有更聪明的方法来做到这一点。也许使用模式?有什么建议吗?

这是我刚刚描述的结构的图片:

这是 UML 表示:

这对我来说是一个学习过程。我想了解真正的 Java Guru 如何根据指定的配置实现适当的模块和类。

感谢您的建议。

【问题讨论】:

    标签: java oop design-patterns plug-and-play


    【解决方案1】:

    我正在尝试做一些聪明的事情。我正在创建一个天气应用程序 我们可以用另一个天气 API 替换天气 API 不影响代码库。

    不用再往下看,这第一句话让我想到了plugin architecture design,但是在软件设计的过程中,决不能仓促做出决定,越是拖延,你掌握的信息就越多,更明智的决定可以制作,现在只是一个需要牢记的想法。

    我有一个包含 Interface 类和 Base 的 Base 模块 班级。 Interface 类包含对 API 的调用(所有调用 相似,如果不准确),并且基类包含属性 到 API (同样,所有属性都是相似的,如果不准确的话)。

    当不同的模块共享行为/状态时,重构它们并生成基本抽象类和接口是个好主意,这样您就走在正确的轨道上,但是如果存在差异,则不应将它们重构为基础模块。这背后的原因很简单,可维护性。如果您开始添加 if 子句或开关来处理这些差异,那么您只是引入了模块之间的耦合,并且无论何时添加/修改其他模块,您都必须始终在基本模块中进行更改,这在全部。

    这反映在Open/Closed principle 形式的SOLID principles 中,它表明一个类应该对扩展开放但对修改关闭。

    因此,在您将通用行为重构到基本模块中之后,每个新 API 都应该像您所做的那样扩展基本模块。

    最后,我创建了一个核心模块(包括 main)来实现 我要测试的天气 API 的特定模块类。

    现在,我知道最简单的方法是使用开关 陈述和枚举。但我想知道是否还有更多 聪明的方法来做到这一点。也许使用模式?有什么建议吗?

    确实,使用开关可以让它工作,但它根本不是一个干净的设计,因为和以前一样,在添加、修改或删除模块时,也需要修改这个模块,还有这个代码可能会中断。

    一种可能的解决方案是将这种责任委托给一个新组件,并使用像 Abstract Factory 这样的创建设计模式,它将提供一个接口来实例化组件而无需指定其类。

    至于架构,到目前为止,插件架构仍然有意义,但是如果不同的模块扩展基础合约添加更多功能呢?一种选择是使用Facade pattern 来调整模块调用并提供实现客户期望的接口的输出。

    但话又说回来,根据提供的详细信息,这是我建议的解决方案,但应仔细和更详细地研究该方案,以确保这些工具是适合这项工作的工具,并承诺他们。

    【讨论】:

    • 非常感谢!这正是我所希望的。现在开始研究您的建议。干杯。
    • 嗨,萨尔瓦多!我发现即使我使用的是 Java 的 SPI,正如 René 的回答所解释的那样,我仍然需要使用 Abstraction 来调用插件的适当方法。我希望这个评论对其他人有帮助。再次感谢您提供的信息。 :-)
    • 我发现使用抽象/接口也是避免耦合的好方法,所以我通常经常使用它。很高兴你没事:)
    【解决方案2】:

    除了萨尔瓦多Juan Martinez's answer...

    为了实现插件架构,Java 的Jar File Specification 提供了对服务提供者接口 (SPI) 以及如何查找它们的支持。

    从 Java 1.6 开始。您可以使用ServiceLoader 来查找服务提供商。对于 Java 1.5。更少你必须自己做或使用图书馆。例如。 commons-discovery.

    用法很简单。在您的情况下,在每个插件模块中放置一个 META-INF/services/com.a2i.weatherbase.IWeather 文件。

    在Weather Forecast IO 模块中,文件应该只包含一行

     com.a2i.weatherforecastio.ForecastIO
    

    该行必须是IWeather 实现类的完整限定名。

    对其他模块执行相同操作,您可以通过 ServiceLoader 加载实现。

    ServiceLoader<IWeather> weatherServicesLoader = ServiceLoader.load(IWeather.class);
    Iterator<IWeather> weatherServices = weatherServicesLoader.iterator();
    

    现在取决于您的运行时类路径,将找到多少服务。尝试从类路径中添加和删除模块 jar 存档并运行您的应用程序。

    编辑 我写了一篇关于使用标准 java 的可插拔架构的博客。见http://www.link-intersystems.com/blog/2016/01/02/a-plug-in-architecture-implemented-with-java/

    源代码也可以在https://github.com/link-intersystems/blog/tree/master/java-plugin-architecture获得

    【讨论】:

    • 因为我建议了插件架构,但也指出没有更多细节,不能确定它是最好的选择,不想更进一步,但无论如何,真的很好的补充 :)
    • 嘿,René,我决定使用你的方法,但我做错了。我正在尝试在 Netbeans 中运行 JUnit 测试,而我的迭代器为 0,所以我不能加载我的服务类。我的依赖项指向我在 IDE 中的其他项目。我还需要把罐子放在某个地方吗?
    • 很棒的博客,顺便说一句。我下载了您的代码并将其用作我的示例。我创建了一个单元测试并尝试运行您的项目,并且迭代器也为 0。我想我在这里找到了答案:stackoverflow.com/a/29205130/1735836
    • 嘿,René,我在使用 ServiceLoader 进行多模块项目时遇到了困难。所以,我在这里提出另一个问题:stackoverflow.com/q/34619176/1735836
    • @MissLucy 好的,我马上去看看。
    【解决方案3】:

    一种解决方案是您必须使用所有已识别的常用操作来定义常用接口。扩展/插件需要实现该接口,并且必须为常用操作提供实现。

    您可以使用抽象工厂设计模式在运行时根据输入参数连接确切的实现。

    接口和抽象类在这种情况下总是很好,谢谢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-07
      • 2017-01-07
      • 2013-08-16
      • 2020-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多