简体   繁体   English

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

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

I often find I want to do something like this:我经常发现我想做这样的事情:

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

To force a subclasses of Foo to return a parameter.强制 Foo 的子类返回参数。

I know you can't do it and I know why you can't do it but the common alternative of:我知道你不能这样做,我知道你为什么不能这样做,但常见的替代方法是:

class Foo{
public abstract String getParam();
}

Is unsatisfactory because it requires you to have an instance which is not helpful if you just want to know the value of the parameter and instantiating the class is expensive.令人不满意,因为它要求您拥有一个实例,如果您只想知道参数的值并且实例化类的成本很高,那么这无济于事。

I'd be very interested to know of how people get around this without getting into using the "Constant Interface" anti pattern.我很想知道人们如何在不使用“常量接口”反模式的情况下解决这个问题。

EDIT: I'll add some more detail about my specific problem, but this is just the current time when I've wanted to do something like this there are several others from the past.编辑:我会添加一些关于我的具体问题的更多细节,但这只是我想做这样的事情的当前时间,过去还有其他几个。

My subclasses are all data processors and the superclass defines the common code between them which allows them to get the data, parse it and put it where it needs to go.我的子类都是数据处理器,超类定义了它们之间的通用代码,允许它们获取数据、解析数据并将其放在需要去的地方。 The processors each require certain parameters which are held in an SQL database.每个处理器都需要保存在 SQL 数据库中的某些参数。 Each processor should be able to provide a list of parameters that it requires and the default values so the configuration database can be validated or initialised to defaults by checking the required parameters for each processor type.每个处理器应该能够提供它需要的参数列表和默认值,以便可以通过检查每种处理器类型所需的参数来验证配置数据库或将其初始化为默认值。 Having it performed in the constructor of the processor is not acceptable because it only needs to be done once per class not once per object instance and should be done at system startup when an instance of each type of class may not yet be needed.在处理器的构造函数中执行它是不可接受的,因为它只需要每个类执行一次,而不是每个对象实例一次,并且应该在系统启动时执行,此时可能还不需要每种类型的类的实例。

The best you can do here in a static context is something like one of the following:在静态上下文中,您可以在这里做的最好的事情类似于以下之一:

a.一种。 Have a method you specifically look for, but is not part of any contract (and therefore you can't enforce anyone to implement) and look for that at runtime:有一个你专门寻找的方法,但不是任何合同的一部分(因此你不能强制任何人实施)并在运行时寻找它:

 public static String getParam() { ... };
 try {
     Method m = clazz.getDeclaredMethod("getParam");
     String param = (String) m.invoke(null);
 }
 catch (NoSuchMethodException e) {
   // handle this error
 }

b.Use an annotation, which suffers from the same issue in that you can't force people to put it on their classes.使用注解,它也有同样的问题,因为你不能强迫人们把它放在他们的班级上。

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

I agree - I feel that this is a limitation of Java.我同意 - 我觉得这是 Java 的一个限制。 Sure, they have made their case about the advantages of not allowing inherited static methods, so I get it, but the fact is I have run into cases where this would be useful.当然,他们已经说明了不允许继承静态方法的优点,所以我明白了,但事实是我遇到了这很有用的情况。 Consider this case:考虑这个案例:

I have a parent Condition class, and for each of its sub-classes, I want a getName() method that states the class' name.我有一个父Condition类,对于它的每个子类,我想要一个getName()方法来说明类的名称。 The name of the sub-class will not be the Java's class name, but will be some lower-case text string used for JSON purposes on a web front end.子类的名称不是 Java 的类名,而是一些用于 Web 前端 JSON 目的的小写文本字符串。 The getName() method will not change per instance, so it is safe to make it static. getName()方法不会因实例而改变,因此将其设为静态是安全的。 However, some of the sub-classes of the Condition class will not be allowed to have no-argument constructors - some of them I will need to require that some parameters are defined at instantiation.但是, Condition类的某些子类将不允许具有无参数构造函数 - 其中一些我将需要要求在实例化时定义某些参数。

I use the Reflections library to get all classes in a package at runtime.我使用Reflections库在运行时获取包中的所有类。 Now, I want a list of all the names of each Condition class that is in this package, so I can return it to a web front end for JavaScript parsing.现在,我想要一个包含此包中每个Condition类的所有名称的列表,以便我可以将其返回到 Web 前端以进行 JavaScript 解析。 I would go through the effort of just instantiating each class, but as I said, they do not all have no-argument constructors.我会努力实例化每个类,但正如我所说,它们并非都具有无参数构造函数。 I have designed the constructors of the sub-classes to throw an IllegalArgumentException if some of the parameters are not correctly defined, so I cannot merely pass in null arguments.我已经设计了子类的构造函数,如果某些参数定义不正确,则会抛出IllegalArgumentException ,因此我不能仅仅传入空参数。 This is why I want the getName() method to be static, but required for all sub-classes.这就是为什么我希望getName()方法是静态的,但所有子类都需要。

My current workaround is to do the following: In the Condition class (which is abstract), I have defined a method:我当前的解决方法是执行以下操作:在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()");
}

So in each sub-class, I simply define:所以在每个子类中,我简单地定义:

@Override
public String getName () {
    return getConditionName ();
}

And then I define a static getConditionName() method for each.然后我为每个定义了一个静态getConditionName()方法。 This is not quite "forcing" each sub-class to do so, but I do it in a way where if getName() is ever inadvertently called, the programmer is instructed how to fix the problem.这并不是完全“强迫”每个子类这样做,但我这样做的方式是,如果无意中调用了 getName(),则会指示程序员如何解决问题。

It seems to me you want to solve the wrong problem with the wrong tool.在我看来,您想用错误的工具解决错误的问题。 If all subclasses define (can't really say inherit) your static method, you will still be unable to call it painlessly (To call the static method on a class not known at compile time would be via reflection or byte code manipulation).如果所有子类都定义(不能真正说继承)您的静态方法,您仍然无法轻松调用它(要在编译时未知的类上调用静态方法将通过反射或字节码操作)。

And if the idea is to have a set of behaviors, why not just use instances that all implement the same interface?如果想法是有一组行为,为什么不使用所有实现相同接口的实例? An instance with no specific state is cheap in terms of memory and construction time, and if there is no state you can always share one instance (flyweight pattern) for all callers.一个没有特定状态的实例在内存和构建时间方面很便宜,如果没有状态,你总是可以为所有调用者共享一个实例(享元模式)。

If you just need to couple metadata with classes, you can build/use any metadata facility you like, the most basic (by hand) implementation is to use a Map where the class object is the key.如果您只需要将元数据与类耦合,您可以构建/使用您喜欢的任何元数据工具,最基本的(手动)实现是使用 Map ,其中类对象是关键。 If that suits your problem depends on your problem, which you don't really describe in detail.如果这适合您的问题取决于您的问题,您并没有真正详细描述。

EDIT: (Structural) Metadata would associate data with classes (thats only one flavor, but probably the more common one).编辑:(结构)元数据会将数据与相关联(这只是一种风格,但可能是更常见的一种)。 Annotations can be used as very simple metadata facility (annotate the class with a parameter).注释可以用作非常简单的元数据工具(用参数注释类)。 There are countless other ways (and goals to achieve) to do it, on the complex side are frameworks that provide basically every bit of information designed into an UML model for access at runtime.有无数其他方法(和要实现的目标)来做到这一点,在复杂的方面,框架基本上提供了设计到 UML 模型中的每一位信息,以便在运行时访问。

But what you describe (processors and parameters in database) is what I christened "set of behaviors".但是你所描述的(数据库中的处理器和参数)就是我所谓的“行为集”。 And the argument "parameters need to be loaded once per class " is moot, it completely ignores the idioms that can be used to solve this without needing anything 'static'.并且“每个类需要加载一次参数”的论点没有实际意义,它完全忽略了可用于解决此问题的习语,而无需任何“静态”。 Namely, the flyweight pattern (for having only once instance) and lazy initialization (for doing work only once).即,享元模式(仅具有一次实例)和延迟初始化(仅执行一次工作)。 Combine with factory as needed.根据需要与工厂结合。

I'm having the same problem over and over again and it's hard for me to understand why Java 8 preferred to implement lambda instead of that.我一遍又一遍地遇到同样的问题,我很难理解为什么 Java 8 更喜欢实现 lambda 而不是那个。

Anyway, if your subclasses only implement retrieving a few parameters and doing rather simple tasks, you can use enumerations as they are very powerful in Java: you can basically consider it a fixed set of instances of an interface.无论如何,如果您的子类只实现检索几个参数并执行相当简单的任务,您可以使用枚举,因为它们在 Java 中非常强大:您基本上可以将其视为接口的一组固定实例。 They can have members, methods, etc. They just can't be instanciated (as they are "pre-instanciated").它们可以有成员、方法等。它们不能被实例化(因为它们是“预实例化的”)。

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());
    }
}

The nice thing is that you can get all "instances" by calling Processor.values() :好消息是您可以通过调用Processor.values()来获取所有“实例”:

for (Processor p : Processorvalues()) {
    System.out.println(String.format("Param %s: %s", p.name(), p.getParam()));
    p.doProcessing();
}

If the processing is more complex, you can do it in other classes that are instanciated in the enum methods:如果处理更复杂,您可以在枚举方法中实例化的其他类中进行:

@Override
public String getParam() {
    return new LookForParam("text").getParam();
}

You can then enrich the enumeration with any new processor you can think of.然后,您可以使用您能想到的任何新处理器来丰富枚举。

The down side is that you can't use it if other people want to create new processors, as it means modifying the source file.不利的一面是,如果其他人想要创建新的处理器,则不能使用它,因为这意味着要修改源文件。

You can use the factory pattern to allow the system to create 'data' instances first, and create 'functional' instances later.您可以使用工厂模式让系统先创建“数据”实例,然后再创建“功能”实例。 The 'data' instances will contain the 'mandatory' getters that you wanted to have static . 'data' 实例将包含您想要static的'强制'getter。 The 'functional' instances do complex parameter validation and/or expensive construction. “功能”实例进行复杂的参数验证和/或昂贵的构造。 Of course the parameter setter in the factory can also so preliminary validation.当然工厂里的参数设置器也可以这样初步验证。

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