java - 根据传递的参数选择接口(interface)的实现

标签 java interface

我有一个父界面。

public interface Parent{...}

以及两个实现该接口(interface)的类。

public class ChildA implements Parent {...}
public class ChildB implements Parent {...}

现在我想根据传递的参数创建子对象的对象。像这样的事情,

Parent instance1 = Parent.getChild("Key1");

目前,我通过使用 HashMap 来实现这一点。

static Map<String,Class<?>> map = new HashMap<String,Class<?>>(); 
    
static void init() throws ClassNotFoundException {
    map.put("Key1",Class.forName("ChildA"));
    map.put("Key2",Class.forName("ChildB"));
}
    
static Parent getChild(String key) throws InstantiationException, IllegalAccessException {
    return (Parent) map.get(key).newInstance();
}

但这里的问题是每次我实现一个新的 child 时,我都必须将其添加到 Parent init 方法中。那么有没有更干净的方法来做到这一点?比如将 key 的信息添加到 child 本身,

public class ChildA implements Parent {
    
    private String key = "Key1";
    ...

因此,当我从父类调用 getChild 时,它会引用相应的 Child。

基本上,我要求一种根据传递的参数动态引用父级的子级的方法。

最佳答案

您可以使用service provider mechanism 。当积极使用 Java 的模块系统时,这种方法效果最好,必要的部分甚至集成到了 Java 语言中。

如果可以在其他模块中实现,则包含父接口(interface)的模块必须导出其包。然后,想要查找接口(interface)实现的模块必须有一个

uses yourpackage.Parent;

模块信息中的指令。提供实现的模块必须有一个类似的指令

provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;

在其模块信息中。可以使用接口(interface)并在同一模块内提供实现。这也可能是包含接口(interface)声明的模块。在这种情况下,甚至可以仅在单个模块内使用该机制,而根本不导出接口(interface)。

编译器将检查指定的实现类是否为 public、是否具有 public 无参数构造函数,以及是否真正实现了服务接口(interface)。这些实现不需要驻留在导出的包中;该接口(interface)可能是从另一个模块访问实现的唯一方法。

由于编译器会预先检查所需的不变量,因此您无需处理反射固有的问题。例如,您不需要声明或处理 InstantiationExceptionIllegalAccessException

一个简单的设置可能是

模块信息
module ExampleApp {
    uses yourpackage.Parent;
    provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
}
你的包裹/ parent
package yourpackage;

public interface Parent {
}

你的包裹/TheKey
package yourpackage;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheKey {
    String value();
}
你的包裹/ChildA
package yourpackage;

@TheKey("Key1")
public class ChildA implements Parent {
}
你的包裹/ChildB
package yourpackage;

@TheKey("Key2")
public class ChildB implements Parent {
}
独立包/使用接口(interface)
package independentpackage;

import java.util.ServiceLoader;
import yourpackage.Parent;
import yourpackage.TheKey;

public class UsingTheInterfaces {
    public static void main(String[] args) {
        Parent p = getChild("Key1");
        System.out.println(p);
    }
    static Parent getChild(String key) {
        return ServiceLoader.load(Parent.class).stream()
            .filter(p -> {
                TheKey keyAnno = p.type().getAnnotation(TheKey.class);
                return keyAnno != null && keyAnno.value().equals(key);
            })
            .findAny()
            .map(ServiceLoader.Provider::get)
            .orElse(null); // decide how to handle absent keys
    }
}

如上所述,当您有一个 exports yourpackage; 指令时,其他模块和 ServiceLoader 可以提供实现。将为运行时存在的所有模块动态发现这些实现。使用模块不需要在编译时知道它们。

对于没有模块系统的旧 Java 版本,有一个前身机制,它也由 ServiceLoader 以向后兼容的方式处理。这些提供程序必须打包在一个 jar 文件中,该文件包含一个文件 META-INF/services/yourpackage.Parent,该文件包含实现该接口(interface)的 jar 文件中所有类的列表。在 Java 9 之前,ServiceLoader 还缺乏 Stream API 支持,该支持允许在实例化实现之前查询注释。您只能使用已经实例化类并返回实例的Iterator

避免旧 API 不必要的开销的一种方法是将接口(interface)分为服务提供者接口(interface)和实际服务接口(interface)。提供者接口(interface)将提供元信息并充当实际服务实现的工厂。与CharsetProvider之间的关系进行比较和 CharsetFileSystemProviderFileSystem 。这些例子也表明服务提供者机制已经被广泛使用。更多示例可以在 java.base module documentation 中找到.

这只是一个概述; API documentation of ServiceLoader包含有关该机制的更多详细信息。

关于java - 根据传递的参数选择接口(interface)的实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62752219/

相关文章:

java - java的LinkedList类中的元素是否有索引?

java - Jackson JSON、不可变类和接口(interface)

dependency-injection - MEF 和单独的接口(interface)组件导致 "Interface for every class"

java - 有没有办法编译gcc来支持单一语言?

java - Greenfoot/Java - 不兼容的类型 : boolean cannot be converted to int

c# - 在 C# 中,有没有办法让类使用接口(interface)或抽象类定义字段?

java - 错误消息 : The method sort(List<T>) in the type Collections is not applicable for the arguments (ArrayList<Date>)

c# - 为什么在类中实现的 C# 接口(interface)方法必须是公共(public)的?

java - 发送请求(camel-http)后如何处理错误?

java - 带有下拉菜单和文本输入的多部分 Java 表单