我经常发现我想做这样的事情:
class Foo{
public static abstract String getParam();
}
强制Foo的子类返回参数。
我知道你不能这样做,我知道为什么你不能这样做但是常见的选择:
class Foo{
public abstract String getParam();
}
不满意是因为如果你只想知道参数的值并且实例化类是昂贵的,它需要你有一个没用的实例。
我很想知道人们如何在不使用“常量接口”反模式的情况下解决这个问题。
编辑:我将添加一些关于我的具体问题的更多细节,但这只是我想要做这样的事情的当前时间,过去还有其他几个。
我的子类是所有数据处理器,超类定义它们之间的公共代码,允许它们获取数据,解析数据并将其放在需要的位置。 每个处理器都需要保存在SQL数据库中的某些参数。每个处理器应该能够提供它所需的参数列表和默认值,以便通过检查每个处理器类型所需的参数来验证配置数据库或将其初始化为默认值。 在处理器的构造函数中执行它是不可接受的,因为它只需要每个类执行一次,而不是每个对象实例一次,并且应该在系统启动时完成,此时可能还不需要每种类型的实例。
答案 0 :(得分:8)
在静态上下文中,您可以做的最好的事情类似于以下之一:
一个。有一个你专门寻找的方法,但不是任何合同的一部分(因此你不能强制执行任何人)并在运行时寻找它:
public static String getParam() { ... };
try {
Method m = clazz.getDeclaredMethod("getParam");
String param = (String) m.invoke(null);
}
catch (NoSuchMethodException e) {
// handle this error
}
湾使用注释,它会遇到同样的问题,因为你不能强迫人们把它放在他们的课堂上。
@Target({TYPE})
@Retention(RUNTIME)
public @interface Param {
String value() default "";
}
@Param("foo")
public class MyClass { ... }
public static String getParam(Class<?> clazz) {
if (clazz.isAnnotationPresent(Param.class)) {
return clazz.getAnnotation(Param.class).value();
}
else {
// what to do if there is no annotation
}
}
答案 1 :(得分:6)
我同意 - 我觉得这是Java的限制。当然,他们已经说明了不允许继承静态方法的优点,所以我得到了它,但事实是我遇到了这个有用的情况。考虑这种情况:
我有一个父Condition
类,对于每个子类,我想要一个getName()
方法来声明类的名称。子类的名称不是Java的类名,而是在Web前端用于JSON目的的一些小写文本字符串。 getName()
方法不会因每个实例而更改,因此将其设置为静态是安全的。但是,Condition
类的某些子类不允许使用无参数构造函数 - 其中一些我需要在实例化时定义一些参数。
我使用Reflections
库在运行时获取包中的所有类。现在,我想要一个包含此包中每个Condition
类的所有名称的列表,因此我可以将其返回到Web前端以进行JavaScript解析。我会经历只是实例化每个类的努力,但正如我所说,他们并非都有无参数构造函数。如果没有正确定义某些参数,我已经设计了子类的构造函数来抛出IllegalArgumentException
,因此我不能仅传递空参数。这就是为什么我希望getName()
方法是静态的,但是对于所有子类都是必需的。
我目前的解决方法是执行以下操作:在Condition
类(抽象)中,我定义了一个方法:
public String getName () {
throw new IllegalArugmentException ("Child class did not declare an overridden getName() method using a static getConditionName() method. This must be done in order for the class to be registerred with Condition.getAllConditions()");
}
所以在每个子类中,我只是定义:
@Override
public String getName () {
return getConditionName ();
}
然后我为每个人定义一个静态getConditionName()
方法。这并没有“强迫”每个子类这样做,但我这样做的方式是,如果无意中调用了getName(),程序员会被指示如何解决问题。
答案 2 :(得分:3)
在我看来,你想用错误的工具解决错误的问题。 如果所有子类定义(不能真正说继承)你的静态方法,你仍然无法轻松调用它(在一个未知的类上调用静态方法在编译时将通过反射或字节码操作)。
如果想要拥有一组行为,为什么不只使用所有实现相同接口的实例?没有特定状态的实例在内存和构造时间方面是便宜的,如果没有状态,你总是可以为所有调用者共享一个实例(flyweight模式)。
如果您只需要将元数据与类耦合,您可以构建/使用您喜欢的任何元数据工具,最基本的(手工)实现是使用Map类,其中类对象是关键。如果这适合你的问题取决于你的问题,你没有详细描述。
编辑:(结构)元数据会将数据与类相关联(这只是一种风格,但可能是更常见的风格)。注释可以用作非常简单的元数据工具(使用参数注释类)。有无数其他方法(和实现目标),复杂的一面是框架,它们基本上提供了设计到UML模型中的每一点信息,以便在运行时进行访问。
但你所描述的(数据库中的处理器和参数)就是我所谓的“行为集”。而且“参数需要加载一次每个类”的论点没有实际意义,它完全忽略了可以用来解决这个问题而不需要任何“静态”的习语。即,flyweight模式(仅用于一次实例)和延迟初始化(仅用于工作一次)。根据需要与工厂合并。
答案 3 :(得分:1)
我一遍又一遍地遇到同样的问题,我很难理解为什么Java 8更愿意实现lambda而不是那个。
无论如何,如果您的子类只实现检索一些参数并执行相当简单的任务,您可以使用enumerations,因为它们在Java中非常强大:您基本上可以将它视为一组固定的接口实例。他们可以拥有成员,方法等。他们只是不能实现(因为他们是“预先实例化”)。
public enum Processor {
PROC_IMAGE {
@Override
public String getParam() {
return "image";
}
},
PROC_TEXT {
@Override
public String getParam() {
return "text";
}
}
;
public abstract String getParam();
public boolean doProcessing() {
System.out.println(getParam());
}
}
好消息是,您可以通过调用Processor.values()
来获取所有“实例”:
for (Processor p : Processorvalues()) {
System.out.println(String.format("Param %s: %s", p.name(), p.getParam()));
p.doProcessing();
}
如果处理更复杂,您可以在枚举方法中实例化的其他类中执行此操作:
@Override
public String getParam() {
return new LookForParam("text").getParam();
}
然后,您可以使用您能想到的任何新处理器来丰富枚举。
缺点是,如果其他人想要创建新的处理器,则无法使用它,因为这意味着修改源文件。
答案 4 :(得分:0)
您可以使用工厂模式使系统首先创建“数据”实例,然后再创建“功能”实例。 “数据”实例将包含您希望拥有static
的“强制性”吸气剂。 “功能”实例执行复杂的参数验证和/或昂贵的构造。当然,工厂中的参数设置器也可以进行初步验证。
public abstract class Processor { /*...*/ }
public interface ProcessorFactory {
String getName(); // The mandatory getter in this example
void setParameter(String parameter, String value);
/** @throws IllegalStateException when parameter validation fails */
Processor construct();
}
public class ProcessorA implements ProcessorFactory {
@Override
public String getName() { return "processor-a"; }
@Override
public void setParameter(String parameter, String value) {
Objects.requireNonNull(parameter, "parameter");
Objects.requireNonNull(value, "value");
switch (parameter) {
case "source": setSource(value); break;
/*...*/
default: throw new IllegalArgumentException("Unknown parameter: " + parameter);
}
}
private void setSource(String value) { /*...*/ }
@Override
public Processor construct() {
return new ProcessorAImpl();
}
// Doesn't have to be an inner class. It's up to you.
private class ProcessorAImpl extends Processor { /*...*/ }
}