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!!!!
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 Function
s only as a convenience if your application needs to transform whole collections of objects at once a lot.
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:
- Have a single "source of truth" to go to for all objects of the given Base class.
- Allow the creation of an instance of a given object every time you request one.
- 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.
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.
精彩评论