设计帮助! enum工厂变压器中的java泛型!

时间:2010-11-30 19:31:32

标签: java generics rtti

我想知道我是否可以通过一种很好的方式来设计这个。我会采用我的方法,但我认为有更好的解决方案(因此问题:))。

我想创建一个枚举(清除选项并避免使用单例体系结构),它具有从另一个对象创建一个对象的访问器。但那些对象是非常灵活的。

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

让我进入一点层次结构。如果我从各种各样的对象转到这样的东西:

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可能会发生变化(随着sys的增长)。在所有这些情况下我都可以返回一个“基地”,但它需要一个演员和一个支票。我宁愿没有那个。

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

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

3 个答案:

答案 0 :(得分:2)

这不是一个非常好的设计,我甚至无法分辨这个枚举试图完成的内容。首先,您使用每个枚举值实现的通用方法,这意味着方法的调用者可以决定他们想要的类型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();

这比enum版本更清晰,不易出错。

如果有必要,您可以执行各种操作以简化此操作,例如创建定义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。这是最接近您的原始设计,但它是类型安全的,适用于所有Function - 接受Guava提供的实用程序。您可以根据需要在类中收集这些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方法并提供此类{ {1}}只是为了方便您的应用程序需要一次性转换整个对象集合。

答案 1 :(得分:0)

关于泛型,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. 具有类型安全性并避免在运行时进行铸造。
  4. 除非你有其他要求我没想到,否则工厂似乎更可取:

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

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

答案 2 :(得分:0)

我花了一些时间,最终设法实现了基于enum的工厂,看起来像你在寻找什么。

以下是我工厂的源代码:

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

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