繁体   English   中英

Java - 强制子类具有静态方法的替代方法

[英]Java - Alternatives to forcing subclass to have a static method

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

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

强制 Foo 的子类返回参数。

我知道你不能这样做,我知道你为什么不能这样做,但常见的替代方法是:

class Foo{
public abstract String getParam();
}

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

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

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

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

在静态上下文中,您可以在这里做的最好的事情类似于以下之一:

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

 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
   }
}

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

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

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

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

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

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

我一遍又一遍地遇到同样的问题,我很难理解为什么 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();
}

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

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

您可以使用工厂模式让系统先创建“数据”实例,然后再创建“功能”实例。 'data' 实例将包含您想要static的'强制'getter。 “功能”实例进行复杂的参数验证和/或昂贵的构造。 当然工厂里的参数设置器也可以这样初步验证。

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 { /*...*/ }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM