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

发布于 2024-10-05 02:14:37 字数 1767 浏览 0 评论 0原文

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

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

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

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

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 相当陌生,所以我并不总是确定处理这种情况的最佳方法。

感谢您的任何提示!

I am wondering if I could get some input on a good way to design this. I will put my approach but I think that there is a better solution (hence the question :) ).

I want to create an enum (to make clear the options and to avoid a singleton architecture) that has accessors for creating one object from another. But what those objects are is pretty flexible.

Think of it as a way to limit the number of options for this transformation.

Let me go into a little of the hierarchy. If I am going from a diverse set of objects to something like this:

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

I was thinking of doing something like this:

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

Is this a decent design? This enum will probably grow, and what ValueA and ValueB can be created from might change (as the sys grows). I could return a 'Base' in all those cases, but it would require a cast and a check. I'd prefer to not have that.

Is it necessary for me to have the expectedRtn parameter? Should I be using Generics at all? I am fairly new to Java so I am not always sure the best way to handle this case.

Thanks for any tips!!!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

街道布景 2024-10-12 02:14:37

这不是一个很好的设计,我什至不知道这个枚举想要完成什么。首先,您使用每个枚举值实现的通用方法,这意味着该方法的调用者可以决定他们希望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 并创建一个 函数。这是最接近您的原始设计的,但它是类型安全的,并且可以与 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 方法,并提供此类 Function 只是为了方便一下子很多。

This isn't a very good design and I really can't even tell what this enum is trying to accomplish. To start with, you're using generic methods that each enum value implements, which means the caller of the method gets to decide what type they want T to be... but that's not what you want, because the methods are in fact opinionated about what types of objects they'll return.

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

The above is totally legal given your code, but your code does not actually handle this. Generic methods don't do what you seem to think they do.

I feel like what you actually want is just a simple way to transform objects of specific types to objects of type ValueA or ValueB. The simplest way to do this is just to have each class that can be transformed in this way provide a method that does that on each such class:

public class CanBeTranslatedToB {
  ...

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

Then, if you have an instance of CanBeTranslatedToB, rather than doing:

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

you'd just do:

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

That's much clearer and not error-prone like the enum version.

If necessary, you can do various things to make this easier such as making an interfaces that define the toValueA() and toValueB() methods and making helper classes to provide any common behavior that all implementations need to use. I don't see any use for an enum like you describe.

Edit:

If you can't change the code for the classes that need to be transformed to ValueB etc., you have several options. The simplest (and probably best, in my opinion) way to handle that would be to add factory methods to ValueA and ValueB such as:

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

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

Then you can just write:

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

If you don't want those methods on ValueB, you could have them in another class with method names like newValueB(CanBeTranslatedToB).

Finally, another option would be to use Guava and create a Function for each conversion. This is the closest to your original design, but it is type safe and works well with all the Function-accepting utilities Guava provides. You could collect these Function implementations in classes as you see fit. Here's an example of a singleton implementing a conversion from Foo to ValueB:

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

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

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

However, I wouldn't use this as the only way to do the conversion... it would be better to have the static valueOf methods I mentioned above and provide such Functions only as a convenience if your application needs to transform whole collections of objects at once a lot.

勿忘初心 2024-10-12 02:14:37

关于泛型,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. 对于给定基类的所有对象,有一个单一的“事实来源”。
  2. 允许在每次请求时创建给定对象的实例。
  3. 具有类型安全并避免在运行时进行强制转换。

除非你有我没有想到的其他要求,否则工厂似乎会更好:

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

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

Regarding Generics, Java doesn't have "real" generics, which can be both beneficial and detrimental in this case. Using generics is tricky when you don't know at compile time exactly what type of object you're dealing with. If the code consuming this information actually knows which type of object it's supposed to expect from a call to ValueTransformer.ValueA.createVo, then it should honestly be expected to cast the returned value. I would expect the call to look more like this:

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

If I'm getting the wrong type out of this method, I would rather see a Cast exception on this line (where the problem really happened) than a null pointer exception later on. This is correct "fail-fast" practice.

If you really don't like the explicit casting, I've seen a cool trick that lets you cast these things implicitly. I think it goes something like this:

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

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

However, I don't really recommend this approach because it still performs the cast at runtime, but nobody would suspect that by looking at your usage code.

I can see a few goals that you may be hoping to achieve:

  1. Have a single "source of truth" to go to for all objects of the given Base class.
  2. Allow the creation of an instance of a given object every time you request one.
  3. Have type-safety and avoid casting at runtime.

Unless you have other requirements I'm not thinking of, it seems like a factory would be preferable:

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

This satisfies all the requirements mentioned above. Furthermore, if you know what type of object is required to produce a ValueA object, you can use a more explicit type on the input value.

多彩岁月 2024-10-12 02:14:37

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

这是我工厂的源代码:

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)必须传递给工厂方法本身的原因。此外,工厂实现本身包含会产生警告的强制转换。但从另一方面来看,至少使用这个工厂的代码足够干净并且不会产生警告。

I spent some time and finally managed to implement enum based factory that looks like what you are looking for.

Here is the source code of my factory:

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

Here is how I use it.

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

Personally I do not like too much this implementation. Enum is defined as Enum, so it cannot be parametrized on class level. This is the reason that classes-parameters (Thread and Socket in my example) must be passed to factory method itself. Also the factory implementation itself contains casting that produces warning. But from other hand at least code that uses this factory is clean enough and does not produce warnings.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文