【问题标题】:Design Help! java generics in a enum factory transformer!设计帮助!枚举工厂转换器中的 java 泛型!
【发布时间】:2010-11-30 19:31:31
【问题描述】:

我想知道是否可以就设计这个的好方法获得一些意见。我会提出我的方法,但我认为有更好的解决方案(因此问题:))。

我想创建一个枚举(以明确选项并避免单例架构),它具有用于从另一个对象创建一个对象的访问器。但是这些对象是非常灵活的。

将其视为一种限制此转换选项数量的方法。

让我稍微介绍一下层次结构。如果我要从一组不同的对象变成这样的对象:

class Base {...}
class ValueA extends Base {...}
class ValueB extends Base {...}

我正在考虑做这样的事情:

public enum ValueTransformer{
  VALUE_A{

    @Override
    public <T> T createVo (Class<T> expectedRtn, Object obj) {
      ValueA retObj = null;

      if (expectedRtn == getReturnType ()) {
        if (obj != null && CanBeTranslatedToA.class == obj.getClass ()) {
          retObj = new ValueA ();
          /*...*/
        }
      }

      return retObj;
    }

    @Override
    public Class<ValueA> getReturnType () { return ValueA.class; }

  },
  VALUE_B {
    @Override
    public Class<ValueB> getReturnType () { return ValueB.class; }

    @Override
    public <T> T createVo (Class<T> expectedRtn, Object obj) {
      ValueB retObj = null;

      if (expectedRtn == getReturnType ()) {
        if (obj != null && CanBeTranslatedToB.class == obj.getClass ()) {
          retObj = new ValueB ();
          /*...*/
        }  else if (obj != null && AnotherClassForB.class = obj.getClass ()){
          retObj = new ValueB();
          /* ... */
        }
      }

      return retObj;
    }
  };

  public abstract <T> Class<T> getReturnType ();
  public abstract <T> T createVo (Class<T> expectedRtn, Object obj);
}

这是一个不错的设计吗?这个枚举可能会增长,并且可以创建的 ValueA 和 ValueB 可能会改变(随着系统的增长)。在所有这些情况下,我都可以返回一个“Base”,但它需要一个演员表和一个检查。我宁愿没有那个。

我有必要拥有 expectedRtn 参数吗?我应该使用泛型吗?我对 Java 还很陌生,所以我并不总是确定处理这种情况的最佳方法。

感谢您的任何提示!!!!

【问题讨论】:

  • 你能给出一个代码示例来说明如何使用这些枚举值吗?
  • 很抱歉,但这看起来像是过度设计。既然调用者需要知道“预期的返回类型”,他不妨强制转换一下。
  • 进行演员阵容会更好吗?我认为选角是一个很大的问题。有点天生危险。我很容易出错。这样做意味着开发人员仍然必须知道他们在做什么,但他们无法判断他们是否错了。强制错误的演员阵容是很有可能的,对吧?

标签: java generics rtti


【解决方案1】:

这不是一个很好的设计,我什至不知道这个枚举试图完成什么。首先,您使用的是每个枚举值实现的通用 方法,这意味着方法的调用者可以决定他们想要 T 的类型......但这不是你的想要,因为这些方法实际上对于它们将返回什么类型的对象是自以为是的。

Class<String> foo = ValueTransformer.VALUE_B.getReturnType();
String string = ValueTransformer.VALUE_A.createVo(String.class, "");

鉴于您的代码,上述内容是完全合法的,但您的代码实际上并没有处理这个问题。泛型方法并不像您认为的那样做。

我觉得你真正想要的只是将特定类型的对象转换为ValueAValueB 类型的对象的简单方法。最简单的方法是让每个可以以这种方式转换的类都提供一个对每个这样的类执行此操作的方法:

public class CanBeTranslatedToB {
  ...

  public ValueB toValueB() {
    ValueB result = new ValueB();
    ...
    return result;
  }
}

那么,如果你有一个CanBeTranslatedToB 的实例,而不是这样做:

 CanBeTranslatedToB foo = ...
 ValueB b = ValueTransformer.VALUE_B.createVo(ValueB.class, foo);

你会这样做:

CanBeTranslatedToB foo = ...
ValueB b = foo.toValueB();

这样更清晰,不像枚举版本那样容易出错。

如有必要,您可以做各种事情来简化此操作,例如创建定义 toValueA()toValueB() 方法的接口,以及创建帮助类以提供所有实现需要使用的任何常见行为。我看不出你描述的枚举有什么用处。

编辑:

如果您无法更改需要转换为ValueB 等的类的代码,您有多种选择。处理该问题的最简单(在我看来可能也是最好的)方法是将工厂方法添加到 ValueAValueB,例如:

// "from" would be another good name
public static ValueB valueOf(CanBeTranslatedToB source) {
  ...
}

public static ValueB valueOf(AnotherClassForB source) {
  ...
}

那么你可以写:

CanBeTranslatedToB foo = ...
ValueB b = ValueB.valueOf(foo);

如果您不想在ValueB 上使用这些方法,可以将它们放在另一个类中,方法名称如newValueB(CanBeTranslatedToB)

最后,另一种选择是使用Guava 并为每次转换创建一个Function。这是最接近您的原始设计的,但它是类型安全的,并且可以与 Guava 提供的所有 Function-accepting 实用程序配合使用。您可以根据需要在类中收集这些Function 实现。这是一个实现从FooValueB 的转换的单例示例:

public static Function<Foo, ValueB> fooToValueB() {
  return FooToValueB.INSTANCE;
}

private enum FooToValueB implements Function<Foo, ValueB> {
  INSTANCE;

  @Override public ValueB apply(Foo input) {
    ...
  }
}

但是,我不会将此作为唯一 方式来进行转换...最好使用我上面提到的静态valueOf 方法并提供这样的Function s 仅在您的应用程序需要一次转换整个对象集合时提供便利。

【讨论】:

  • 我更愿意这样做。但问题是我无权访问“CanBeTranslatedToB”等内部信息……
  • 但我明白你在第一种情况下的观点!我想我只需要做类似的事情: Base createVo (obj) 它更清楚地说明了 null 返回的含义..
  • 感谢编辑!有趣的是我也不能更改 ValueB 对象代码(没有它):( 我正在尝试编写一些简单的方法来创建一个简单的工厂,它没有一堆独特的功能,或者有一个它可以创建的已知 Vo 集合。
  • @thelonesquirrely:如果您无法更改ValueB 的代码,我会将工厂方法放在您拥有的实用程序类中,并使用与ValueB 一起使用的方法。我还强烈建议 not 使用单一方法,该方法采用 Object 并返回 ValueBnull,如果它无法处理给定的对象类型,除非你实际上会用你不知道具体类型的参数来调用它。它破坏了类型安全,使调用变慢并且实际上没有任何优势(因为您可以为每个方法赋予相同的名称,从而使调用它们变得容易)。
  • 谢谢!这就是我要做的。我想知道是否有更好的方法来利用泛型。很多创建方法都是​​相同的,但如果我使用显式参数进行操作,我可以将其推向类型安全
【解决方案2】:

关于泛型,Java 没有“真正的”泛型,这在这种情况下既有利也有弊。当你在编译时不知道你正在处理什么类型的对象时,使用泛型是很棘手的。如果使用此信息的代码实际上知道调用 ValueTransformer.ValueA.createVo 应该期望哪种类型的对象,那么它应该诚实地期望转换返回的值。我希望调用看起来更像这样:

MyTypeA myType = (MyTypeA)ValueTransformer.ValueA.createVo(sourceObject);

如果我从这个方法中得到了错误的类型,我宁愿在这一行(真正发生问题的地方)看到一个 Cast 异常,而不是稍后看到一个空指针异常。这是正确的“快速失败”做法。

如果您真的不喜欢显式转换,我看到了一个很酷的技巧,可以让您隐式转换这些东西。我认为它是这样的:

public abstract <T> T createVo (Object obj) {...}

MyTypeA myType = ValueTransformer.ValueA.createVo(sourceObject);

但是,我并不真正推荐这种方法,因为它仍然在运行时执行强制转换,但没有人会通过查看您的使用代码来怀疑这一点。

我可以看到一些您可能希望实现的目标:

  1. 为给定 Base 类的所有对象提供一个“事实来源”。
  2. 允许在您每次请求时创建给定对象的实例。
  3. 具有类型安全性并避免在运行时强制转换。

除非您有其他我没有想到的要求,否则工厂似乎更可取:

public class ValueFactory
{
    public ValueA getValueA(Object obj) {return new ValueA();} 
    public ValueB getValueB(Object obj) {return new ValueB();}
}

这满足了上述所有要求。此外,如果您知道生成 ValueA 对象需要什么类型的对象,则可以在输入值上使用更明确的类型。

【讨论】:

    【解决方案3】:

    我花了一些时间,终于设法实现了基于枚举的工厂,看起来就像您正在寻找的那样。

    这是我工厂的源代码:

    import java.net.Socket;
    
    public enum EFactory {
        THREAD(Thread.class) {
            protected <T> T createObjectImpl(Class<T> type) {
                return (T)new Thread();
            }
        },
        SOCKET(Socket.class) {
            protected <T> T createObjectImpl(Class<T> type) {
                return (T)new Socket();
            }
        },
        ;
    
        private Class<?> type;
    
        EFactory(Class<?> type) {
            this.type = type;
        }
    
        protected abstract <T> T createObjectImpl(Class<T> type);
    
        public <T> T createObject(Class<T> type) {
            return assertIfWrongType(type, createObjectImpl(type));
        }
    
        public <T> T assertIfWrongType(Class<T> type, T obj) {
            if (!type.isAssignableFrom(obj.getClass())) {
                throw new ClassCastException();
            }
            return obj;
        }
    }
    

    这是我的使用方法。

    Thread t1 = EFactory.THREAD.createObject(Thread.class);
    String s1 = EFactory.THREAD.createObject(String.class); // throws ClassCastException
    

    我个人不太喜欢这种实现方式。 Enum 被定义为 Enum,因此不能在类级别上对其进行参数化。这就是必须将类参数(在我的示例中为 Thread 和 Socket)传递给工厂方法本身的原因。工厂实现本身也包含产生警告的强制转换。但从另一方面来看,至少使用这个工厂的代码足够干净并且不会产生警告。

    【讨论】:

    • 我喜欢这种方法!我正在尝试制作 lib 类型的东西。我不太担心使内部复杂化,只要使用它简单。我想尝试确保所有内容都是类型安全的,如果可能的话,在编译时。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多