I was wondering what would be the most appealing way of achieving this goal.
I have a blackbox. I give it a supported type
and it returns a random value base off of the type.
For my supported types, I have an enum
defined as follows:
public enum Types
{
INTEGER,
DOUBLE,
BIGINTEGER,
BIGDECIMAL,
STRING,
BOOLEAN,
CHAR
}
I thought the easiest way would be to create a static function which returns an Object based off of the Type
public static Object getRandomValue(Types type)
{
switch(type)
{
case INTEGER:
return randomInt();
case DOUBLE:
return randomDouble();
case BIGINTEGER:
return randomBigInt();
case BIGDECIMAL:
return randomBigDecimal();
case STRING:
return randomString();
case BOOLEAN:
return randomBool();
case CHAR:
return randomChar();
}
}
The problem is that an additional cast would have to be made each time that I want to retrieve a random value based off of this method.
I've looked into the design patterns abstract factory
and factory
, and I can't decide if there's an advantage into implementing either of those. Or if there's a different design pattern that seems to be more appropriate.
Assume that all my random
methods are defined.
-
1\$\begingroup\$ See the Command pattern to avoid countless if-else-if statements: stackoverflow.com/a/4480360/59087 \$\endgroup\$Dave Jarvis– Dave Jarvis2013年09月12日 21:02:39 +00:00Commented Sep 12, 2013 at 21:02
2 Answers 2
Firstly if you are going to be passing objects of type Object
around I guess you best be prepared to do a few casts here and there. You can achieve this using a simple interface and templates quite easily.
Interface:
public interface RandomGenerator <T> {
T getRandom();
}
Implementation:
public class DoubleRandomGenerator implements RandomGenerator<Double> {
@Override
public Double getRandom() {
//do what you do
return 0d;
}
}
Next, don't try to return every type of Object from the one Switch statement, it will always be ugly. In order to work out the next step maybe you could elaborate on what is happening outside the blackbox, e.g. if the calling code wants a Double you could add that into your enum:
public enum Types
{
INTEGER(new IntegerRandomGenerator()),
DOUBLE(new DoubleRandomGenerator()),
BIGINTEGER(...),
BIGDECIMAL(...),
STRING(...),
BOOLEAN(...),
CHAR(...);
private RandomGenerator generator;
private Types(RandomGenerator generator) {
this.generator = generator;
}
public RandomGenerator getGenerator() {
return generator;
}
}
The main difference here is that you have deferred the creation of the random value to the callers end. The calling code will also be required to either use generics too:
RandomGenerator<Double> g = Types.DOUBLE.getGenerator();
Double d = g.getRandom();
Or generate warnings and cast the result:
RandomGenerator g = Types.DOUBLE.getGenerator();
Double d = (Double)g.getRandom();
Finally, you say you have read about the factory pattern, that is ultimately what you are playing with here.
Note. I think Enum naming should use the singular (Type) rather than the plural (Types), you are not selecting an Object of type Types as your Object (Types.DOUBLE) represents just one type.
-
\$\begingroup\$ This will still require some casting in the calling code, won't it? Either you have to cast the returned
RandomGenerator
itself, or the result of the call to the generator. \$\endgroup\$tobias_k– tobias_k2013年09月12日 15:22:51 +00:00Commented Sep 12, 2013 at 15:22 -
\$\begingroup\$ Yes, I'll clarify my comment that meant exactly that, the code is neater but you still require either a cast or to add the generic type at the calling end. \$\endgroup\$JohnMark13– JohnMark132013年09月12日 15:27:02 +00:00Commented Sep 12, 2013 at 15:27
1) I don't think you need to define an enum since you can use Boolean.class, Integer.class, etc.
2)
public static <T> T getRandom(Class<T> classs) {
if (classs.equals(Boolean.class)) {
return (T) randomBool();
} else if (classs.equals(Integer.class) {
return (T) randomInt();
// etc.
} else
// Edit: original "else" was: "return null;" changed to:
throw new IllegalStateException("Cannot generate a random " + classs.toString());
}
EDIT
There is also the solution below, but that would still require casting:
public enum Type {
INTEGER {
@Override public Integer getRandom() {
return new randomInt();
}
}; // BOOLEAN, etc.
public abstract Object getRandom();
}
That is basically the same as the original OP code, but where the random method call is within the enum instead of being in a separate method with a switch-statement.
-
\$\begingroup\$ The purpose of the enum is enforce that the function only exists for certain types. \$\endgroup\$Rhs– Rhs2013年09月12日 15:14:39 +00:00Commented Sep 12, 2013 at 15:14
-
1\$\begingroup\$ Then instead of returning null, you could throw an exception when the class is of an unknown type. \$\endgroup\$toto2– toto22013年09月12日 15:17:10 +00:00Commented Sep 12, 2013 at 15:17
-
\$\begingroup\$ @toto2 This is exactly what I was writing up, including the Exception... +1 \$\endgroup\$tobias_k– tobias_k2013年09月12日 15:18:11 +00:00Commented Sep 12, 2013 at 15:18
-
\$\begingroup\$ Your second variant requires casting in the calling code again. I liked your (our) first variant better... \$\endgroup\$tobias_k– tobias_k2013年09月12日 15:26:31 +00:00Commented Sep 12, 2013 at 15:26
-
\$\begingroup\$ @tobias_k Yes, I added that fact in the text. Thanks. \$\endgroup\$toto2– toto22013年09月12日 15:28:17 +00:00Commented Sep 12, 2013 at 15:28