【问题标题】:Java - Alternatives to forcing subclass to have a static methodJava - 强制子类具有静态方法的替代方法
【发布时间】:2012-08-13 16:32:04
【问题描述】:

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

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

强制 Foo 的子类返回参数。

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

class Foo{
public abstract String getParam();
}

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

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

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

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

【问题讨论】:

  • 如果你已经打电话给Class.getName(),为什么还要知道名字?
  • 这是一个例子。我可以说得到 gigawidgets
  • 有趣的问题。你有一个方便的具体例子吗?它可能有助于激发一些想法。 Scala 有一个很好的方法来处理这个问题——它是对象和类之间的区别——这可能对你没有多大帮助。
  • @jeff 我添加了更多细节。问题。我想我可以很容易地在 Scala、Python 或 C# 中做我想做的事,所以令人惊讶的是它们似乎不是 java 等价物。

标签: java


【解决方案1】:

在静态上下文中您可以做的最好的事情是以下之一:

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

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

b.使用注释,它存在同样的问题,因为您不能强迫人们将其放在他们的课程中。

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

【讨论】:

  • 注释很好。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

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

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

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

    【讨论】:

    • 您能否更详细地描述您的最后一段。我在问题中添加了更多细节。我想避免将元数据集中保存,
    • @NickLong 您似乎坚定地采用“静态”方法。你需要调整你的心态。拥有一个类的实例可以打开继承和多态的所有优点。摆脱这样一个函数持有者实例被浪费的想法 - 每个实例约 16 个字节不会使您的应用程序成为一个消耗内存的庞然大物。
    • 您的数据处理器示例 + 参数 调用 用于喷出初始化实例的数据处理器 factory。另外请注意,static 比保持任何东西集中更糟糕(后者至少可以在运行时根据需要进行更改)。静态意味着自动编译时依赖(避免你必须去动态,至少部分地。那么还不如一直去)。
    • 当然是这样。我试图让您相信静态方法在这里是错误的工具 :) 如果您需要静态方法(我不禁想知道您现在是如何绕过静态处理器的?),那么您可以滥用注释(如果参数定义可以用原语/字符串表示),或者如果您真的想要,使用反射来调用静态方法。如果类没有定义所需的方法,只需 throw。我认为没有任何方法可以为此获得编译时错误。
    • 我喜欢你的回答,但我想回应你使用实例的建议。当您需要进行依赖于类的查询时,我经常会发现创建实例并不方便的情况。例如,该类可能是层次结构的抽象父类(选择一个子类并实例化?),或者可能有许多与查询无关的参数来创建具体实例。在这些情况下,这就像制作一个测试模拟以提供查询。我同意一个人只需要解决它并喜欢这里的建议,但我认为 desire 仍然有效。
    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

      您可以使用工厂模式让系统先创建“数据”实例,然后再创建“功能”实例。 “数据”实例将包含您想要拥有的“强制”吸气剂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 { /*...*/ }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-11
        • 2023-03-13
        • 1970-01-01
        • 2022-07-22
        • 2012-04-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多