21

我经常发现我想做这样的事情:

class Foo{
public static abstract String getParam();
}

强制 Foo 的子类返回参数。

我知道你不能这样做,我知道你为什么不能这样做,但常见的选择是:

class Foo{
public abstract String getParam();
}

不能令人满意,因为它要求您拥有一个实例,如果您只想知道参数的值并且实例化该类是昂贵的,那么这将无济于事。

我很想知道人们如何在不使用“常量接口”反模式的情况下解决这个问题。

编辑:我将添加一些关于我的具体问题的更多细节,但这只是我想做这样的事情的当前时间,过去还有其他几个。

我的子类都是数据处理器,超类定义了它们之间的公共代码,允许它们获取数据、解析数据并将其放在需要的地方。每个处理器都需要某些参数,这些参数保存在 SQL 数据库中。每个处理器都应该能够提供它需要的参数列表和默认值,以便通过检查每种处理器类型所需的参数来验证配置数据库或将其初始化为默认值。在处理器的构造函数中执行它是不可接受的,因为它只需要为每个类而不是每个对象实例执行一次,并且应该在系统启动时完成,此时可能还不需要每种类型的类的实例。

4

5 回答 5

11

在静态上下文中,您可以在此处执行的最佳操作类似于以下内容之一:

一个。有一个您专门寻找的方法,但不是任何合同的一部分(因此您不能强制任何人实施)并在运行时寻找它:

 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
   }
}
于 2012-08-13T17:42:38.490 回答
8

我同意 - 我觉得这是 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() 被无意中调用,则指示程序员如何解决问题。

于 2013-09-23T16:03:59.740 回答
3

在我看来,您想用错误的工具解决错误的问题。如果所有子类都定义(不能真正说继承)您的静态方法,您仍然无法轻松调用它(在编译时调用未知类的静态方法将通过反射或字节码操作)。

如果这个想法是要有一组行为,为什么不直接使用实现相同接口的实例呢?没有特定状态的实例在内存和构建时间方面很便宜,如果没有状态,您总是可以为所有调用者共享一个实例(享元模式)。

如果您只需要将元数据与类耦合,您可以构建/使用您喜欢的任何元数据工具,最基本的(手动)实现是使用类对象为键的 Map。如果这适合您的问题取决于您的问题,您并没有真正详细描述。

编辑:(结构)元数据会将数据与相关联(这只是一种风格,但可能是更常见的一种)。注释可以用作非常简单的元数据工具(使用参数注释类)。有无数其他方法(和实现目标)可以做到这一点,在复杂的方面是框架,它们基本上提供了设计到 UML 模型中的每一位信息,以便在运行时访问。

但是您所描述的(数据库中的处理器和参数)就是我所说的“行为集”。并且参数“每个类需要加载一次参数”是没有实际意义的,它完全忽略了可以用来解决这个问题的习语,而不需要任何“静态”。即享元模式(只有一次实例)和延迟初始化(只做一次工作)。根据需要与工厂结合。

于 2012-08-13T17:13:10.887 回答
1

我一遍又一遍地遇到同样的问题,我很难理解为什么 Java 8 更喜欢实现 lambda 而不是那个。

无论如何,如果您的子类只实现检索几个参数并执行相当简单的任务,您可以使用枚举,因为它们在 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();
}

然后,您可以使用您能想到的任何新处理器来丰富枚举。

不利的一面是,如果其他人想创建新的处理器,您将无法使用它,因为这意味着修改源文件。

于 2014-01-31T13:41:38.880 回答
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 { /*...*/ }
}
于 2020-02-11T10:01:48.193 回答