【问题标题】:Alternatives to static methods on interfaces for enforcing consistency用于强制一致性的接口上静态方法的替代方案
【发布时间】:2011-02-17 20:40:07
【问题描述】:

在 Java 中,我希望能够定义标记接口,强制实现提供静态方法。例如,对于简单的文本序列化/反序列化,我希望能够定义一个看起来像这样的接口:

public interface TextTransformable<T>{

  public static T fromText(String text);

  public String toText();

由于 Java 中的接口不能包含静态方法(如许多其他帖子/线程中所述:hereherehere,此代码不起作用。

然而,我正在寻找一些合理的范式来表达相同的意图,即对称方法,其中一个是静态的,由编译器强制执行。目前我们能想到的最好的方法是某种静态工厂对象或泛型工厂,这两者都不是真正令人满意的。

注意:在我们的例子中,我们的主要用例是我们有很多很多“值对象”类型 - 枚举或其他具有有限数量值的对象,通常不携带超出其值的状态,我们每秒解析/反解析数千次,因此实际上要关心重用实例(如 Float、Integer 等)及其对内存消耗/gc 的影响

有什么想法吗?

EDIT1:为了消除一些困惑 - 我们有许多不同的对象适合这种模式 - 实际上我们正在尝试为具有 2 种语义的调用者提供一些优雅的东西:

  • 接口作为契约 - 访问的统一(例如 TextTransformable 作为一种能力)
  • 要求通过子类/实现来实现(例如,强制它们实现自己的转换

就我们对享元、工厂的想法而言——它们都是我们考虑过的选项,实际上我们正在尝试看看是否能找到比依赖 JavaDoc 说“实现工厂和委托调用”更优雅的东西给它,或者按照约定在 XXX 位置暴露它”

【问题讨论】:

    标签: java oop interface


    【解决方案1】:

    只是一个不同的想法。并非适用于所有情况,但可能对其他人有所帮助。

    您可以使用abstract class 作为interfaceconcrete classes 之间的桥梁。抽象类允许静态方法以及契约方法定义。例如,您可以查看 Collection API,其中 Sun 实现了几个抽象类,而不是从零开始对所有具体类进行暴力编码。

    在某些情况下,您可以只用抽象类替换接口。

    【讨论】:

      【解决方案2】:

      一种完全不同的方法(就此而言也是一种丑陋的 hack)是让接口有一个返回方法的方法。

      public interface MyInterface{
          Method getConvertMethod();
      }
      

      现在您的客户端代码可以做到了

      yourInterface.getConvertMethod().invoke(objectToBeConverted);
      

      这非常强大,但 API 设计非常糟糕

      肖恩

      【讨论】:

      • 这实际上很有趣——在 JS 中我会在心跳中完成,或者在 Functor 很常见的其他语言中,虽然在 Java 中感觉有点奇怪
      • 没错,因为方法不是 Java 中的“一等公民”。然而,这可能会在 JDK1.7 中很快改变。我们将看到这会在多大程度上改变编程风格......
      【解决方案3】:

      这真的很适合Flyweight。这基本上就是你试图用静力学来完成的。关于如何为享元对象提供服务以避免创建数千个对象,这里有一些想法。

      一个是工厂,你说你考虑过但拒绝了,虽然你没有说明原因(所以任何其他想法可能会遇到同样的问题)所以我不会去讨论它。

      另一个是让值类型有一个可以为其转换器服务的方法。像这样的:

       public class ValueType {
             public static final TextTransformable<ValueType> CONVERT = ....
       }
      

      然后像这样使用它:

       ValueType value = ValueType.CONVERT.fromText(text);
      
       String text = ValueType.CONVERT.toText(value);
      

      现在这并不强制所有 ValueType 都通过相同的机制提供它们的转换器,因为我认为您需要某种类型的工厂。

      编辑:我想我不知道你觉得工厂有什么不雅点,但我认为你专注于来电者,所以这对你来说是什么感觉:

        ValueType value = getTransformer(ValueType.class).fromText(text);
      

      以上可以通过工厂的静态导入和具有如下签名的方法来完成:

         public static <T> TextTransformable<T> getTransformer(Class<T> type) {
               ...
         }
      

      找到正确转换器的代码不一定是最漂亮的,但从调用者的角度来看,一切都很好。

      编辑 2:进一步考虑这一点,我看到的是您想要控制对象构造。你真的不能那样做。换句话说,在 Java 中,您不能强制实现者使用或不使用工厂来创建他们的对象。他们总是可以公开一个公共构造函数。我认为您的问题是您对执行构建的机制不满意。如果这种理解是正确的,那么下面的模式可能会有用。

      你创建了一个只有私有构造函数的对象,它封装了你的值类型。对象可能有一个泛型类型参数来知道它包装了什么值类型。该对象使用静态工厂方法实例化,该方法采用工厂接口来创建“真实”值对象。所有使用该对象的框架代码都只将此对象作为参数。它不直接接受值类型,并且如果没有值类型的工厂,则无法实例化该对象。

      这种方法的问题在于它的局限性很大。只有一种方法可以创建对象(工厂接口支持的对象),并且使用值对象的能力有限,因为处理这些文本元素的代码只能通过这个对象进行有限的交互。

      我猜他们说没有一个软件问题不能通过额外的间接层来解决,但这可能是一座太过分的桥梁。至少值得深思。

      【讨论】:

      • 是的,我将在上面进行编辑,并附上我们讨论过轻量级的注释——尽管我认为我们专注于访问的统一性(例如,像标记接口,并在子类) - 工厂或静态变压器都不会让我们感到)用一些优雅的东西......
      • 是的,具有讽刺意味的是,我们想要的是限制 ;) 理想情况下更多的是混合,但不必使用合作者对象 - 虽然我同意,你的总结与我们目前的想法相似,可能是很好,因为我们现在可以想出。
      【解决方案4】:

      正如@aperkins 所说,您应该使用枚举。

      枚举基类 Enum 提供了一个 valueOf 方法,可以将字符串转换为实例。

      enum MyEnum { A, B, C; }
      // Here's your toText
      String strVal = MyEnum.A.getName();
      // and here's your fromText
      MyEnum value = MyEnum.valueOf(MyEnum.class, strVal);
      

      更新:对于枚举,这可能会满足您的需求。它使用反射,因此您只需要在必须处理遗留值的枚举上实现 EnumHelper。

      /** Enums can implement this to provide a method for resolving a string
        * to a proper enum name.
        */
      public interface EnumHelp
      {
          // Resolve s to a proper enum name that can be processed by Enum.valueOf
          String resolve(String s);
      }
      
      /** EnumParser provides methods for resolving symbolic names to enum instances.
        * If the enum in question implements EnumHelp, then the resolve method is
        * called to resolve the token to a proper enum name before calling valueOf.
        */
      public class EnumParser
      {
          public static <T extends Enum<T>> T fromText(Class<T> cl, String s) {
              if (EnumHelp.class.isAssignableFrom(cl)) {
                  try {
                      Method resolve = cl.getMethod("resolve", String.class);
                      s = (String) resolve.invoke(null, s);
                  }
                  catch (NoSuchMethodException ex) {}
                  catch (SecurityException ex) {}
                  catch(IllegalAccessException ex) {}
                  catch(IllegalArgumentException ex) {}
                  catch(InvocationTargetException ex) {}
              }
              return T.valueOf(cl, s);
          }
      
          public <T extends Enum<T>> String toText(T value)
          {
              return value.name();
          }
      }
      

      【讨论】:

      • 许多现有对象已经是 Enum,但不幸的是,由于 Enum 不允许覆盖 valueOf(),我们能够生成良好的文本表示(默认值、null 安全、支持过去的版本、编码等) .) 是有限的——因此我们希望实现单独的方法——但具有相似的语义,只是我们的定制能力更强。
      • 如果您不喜欢 valueOf() 的行为,没有什么能阻止您向枚举添加自定义查找方法。如果您需要一个返回 null 或默认值(如果找不到任何内容)的查找方法,您通常会这样做。使用 valueOf() 你只能使用 try / catch 来做到这一点,它将异常处理负责控制流
      【解决方案5】:

      您似乎需要将工厂与创建的对象分开。

      public interface TextTransformer<T> {
          public T fromText(String text);
          public String toText(T source);
      }
      

      您可以随意注册 TextTransformer 类:

      public class FooTextTransformer implements TextTransformer<Foo> {
          public Foo fromText(String text) { return ...; }
          public String toText(Foo source) { return ...; }
      }
      

      如果您希望 FooTextTransformer 成为单例,您可以使用像 Spring 这样的容器来强制执行。 Google 已经启动了一个项目,从他们的代码库中删除所有手动强制执行的单例,但是如果你想做一个老式的单例,你可以使用一个实用程序类:

      public class TextTransformers {
          public static final FooTextTransformer FOO = new FooTextTransformer();
          ...
          public static final BarTextTransformer BAR = new BarTextTransformer();
      }
      

      在您的客户端代码中:

      Foo foo = TextTransformers.FOO.fromText(...);
      ...
      foo.setSomething(...);
      ...
      String text = TextTransformers.FOO.toText(foo);
      

      这只是我想到的一种方法。

      【讨论】:

        【解决方案6】:

        只是一个需要考虑的想法:您可以将转换逻辑与对象本身分开,然后您有一组固定的转换器,实现以下接口:

        public interface Transformer<T>{ 
        
          public T fromText(String text); 
        
          public String toText(T obj); 
        }
        

        您的实际数据类可以有一个方法 getTransformer() 为它们返回正确的转换器。

        【讨论】:

        • 妈妈!你打败我了! :)
        • 我们希望将代码保持在同一位置 - 这些基本上只是价值对象,所以理想情况下这是他们所做工作的重要组成部分
        【解决方案7】:

        如果您在 Java 5 或更高版本中运行,则可以使用 enum 类型 - 根据定义,所有这些都是单例。所以你可以这样:

        public enum MyEnumType {
            Type1,
            Type2,
            //....
            TypeN;
        
            //you can do whatever you want down here
            //including implementing interfaces that the enum conforms to.
        
        }
        

        这样,内存问题就消失了,您可以实现单个行为实例。


        编辑:如果您无权访问枚举(1.4 或更早版本),或者由于某些其他原因,它们不适合您,我建议您使用 Flyweight pattern 实现。

        【讨论】:

        • 您可以在下面看到为什么我们试图提出一个约定以超越 Java 5+ 中枚举支持中内置的功能
        • @jayshao 我们在 valueof 方面遇到了类似的问题,并解决了这个问题。但我认为具有各种单例注册表的享元模式可以为您解决大部分问题,正如@Yishai 进一步解释的那样
        • @jayshao 我可以在今晚晚些时候到那里时看看我家里是否有任何东西。
        • @jayshao:我看到@Yishai 编辑了他的答案,我认为他的蝇量级示例非常好 - 我会看看它。
        • @perkins - 是的,在内部折腾之后,蝇量级的一些变化可能是我们最终的结果,但同样,它的某些部分感觉不对 - 尽管我们是当然受限于语言的表现力
        猜你喜欢
        • 1970-01-01
        • 2011-05-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-25
        • 1970-01-01
        • 1970-01-01
        • 2011-02-12
        • 1970-01-01
        相关资源
        最近更新 更多