or a variant of that which parses a String
. Or both! IfAfter all, if you have the string parsing and you have that createRNG
, connecting the two together should be simple enough, making something like:
Note that the DistributionType
never makes it into the MyRNG
s themselves - they don't need an external enum
to tell them what their type is, because theyinformation about their type is contained in, well, their already knowtype their own types.
or a variant of that which parses a String
. Or both! If you have the string parsing and you have that createRNG
, connecting the two together should be simple enough, making something like
Note that the DistributionType
never makes it into the MyRNG
s themselves - they don't need an external enum
to tell them what their type is, because they already know their own types.
or a variant of that which parses a String
. Or both! After all, if you have the string parsing and you have that createRNG
, connecting the two together should be simple:
Note that the DistributionType
never makes it into the MyRNG
s themselves - they don't need an external enum
to tell them what their type is, because information about their type is contained in, well, their type.
It seems natural to me to more-or-less scrapstart without the enum
, and makeinstead beginning with MyRNG
intoas an interface
. That way, theThe MyRNG
type doesn't know anything about generating random numbers, but it can still guaranteedemand that any object belonging to that type does know how to generate random numbers - whatever subtype they belong to will have to define it for them. Then you can easily add various distributions like
And thanks to subtyping, you can just go MyRNG rng = new GaussianRNG(5.66, 1.2);
, even though MyRNG
has no idea GaussianRNG
exists! NeedAnd when you want another distribution? You can, you just make one!add another subtype, and nobody necessarily needs updating.
Now, you'll notice I kind of dropped the ability to pick a distribution type at runtime. But there's nothing saying you can't have that too! You're definitely allowed to have something likefactory methods à la
public static MyRNG createRNG(DistributionType distribution, double...double[] parameters) {
switch (distribution) {
case UNIFORM_DOUBLE:
if (parameters.length != 2) throw new IllegalArgumentException();
return new UniformDoubleRNG(parameters[0], parameters[1]);
case GAUSSIAN:
if (parameters.length != 2) throw new IllegalArgumentException();
return new GaussianRNG(parameters[0], parameters[1]);
case CHOICE:
return new ChoiceRNG(parameters);
}
}
or a variant of that which parses a String
. Or both! If you have the string parsing and you have that createRNG
, connecting the two together should be simple enough, making something like
public static MyRNG createRNG(String description) {
DistributionType distributionType = typeFromDescription(description);
double[] parameters = parametersFromDescription(description);
return createRNG(distributionType, parameters);
}
Note that the DistributionType
never makes it into the MyRNG
s themselves - they don't need an external enum
to tell them what their type is, because they already know their own types.
It seems natural to me to more-or-less scrap the enum
, and make MyRNG
into an interface
. That way, the MyRNG
type doesn't know anything about generating random numbers, but it can still guarantee that any object belonging to that type does know how to generate random numbers - whatever subtype they belong to will have to define it for them. Then you can easily add various distributions like
And thanks to subtyping, you can just go MyRNG rng = new GaussianRNG(5.66, 1.2);
, even though MyRNG
has no idea GaussianRNG
exists! Need another distribution? You can just make one!
Now, you'll notice I kind of dropped the ability to pick a distribution type at runtime. But there's nothing saying you can't have that too! You're definitely allowed to have something like
public static MyRNG createRNG(DistributionType distribution, double... parameters) {
switch (distribution) {
case UNIFORM_DOUBLE:
if (parameters.length != 2) throw new IllegalArgumentException();
return new UniformDoubleRNG(parameters[0], parameters[1]);
case GAUSSIAN:
if (parameters.length != 2) throw new IllegalArgumentException();
return new GaussianRNG(parameters[0], parameters[1]);
case CHOICE:
return new ChoiceRNG(parameters);
}
}
It seems natural to me to start without the enum
, instead beginning with MyRNG
as an interface
. The MyRNG
type doesn't know anything about generating random numbers, but it can still demand that any object belonging to that type does know how to generate random numbers. Then you can easily add various distributions like
And thanks to subtyping, you can just go MyRNG rng = new GaussianRNG(5.66, 1.2);
, even though MyRNG
has no idea GaussianRNG
exists! And when you want another distribution, you just add another subtype, and nobody necessarily needs updating.
Now, you'll notice I kind of dropped the ability to pick a distribution type at runtime. But there's nothing saying you can't have that too! You're definitely allowed to have factory methods à la
public static MyRNG createRNG(DistributionType distribution, double[] parameters) {
switch (distribution) {
case UNIFORM_DOUBLE:
if (parameters.length != 2) throw new IllegalArgumentException();
return new UniformDoubleRNG(parameters[0], parameters[1]);
case GAUSSIAN:
if (parameters.length != 2) throw new IllegalArgumentException();
return new GaussianRNG(parameters[0], parameters[1]);
case CHOICE:
return new ChoiceRNG(parameters);
}
}
or a variant of that which parses a String
. Or both! If you have the string parsing and you have that createRNG
, connecting the two together should be simple enough, making something like
public static MyRNG createRNG(String description) {
DistributionType distributionType = typeFromDescription(description);
double[] parameters = parametersFromDescription(description);
return createRNG(distributionType, parameters);
}
Note that the DistributionType
never makes it into the MyRNG
s themselves - they don't need an external enum
to tell them what their type is, because they already know their own types.
A lot of the time, if you want to group some objects by a type, the cleanest approach is... well, making types. Classes.
It seems natural to me to more-or-less scrap the enum
, and make MyRNG
into an interface
. That way, the MyRNG
type doesn't know anything about generating random numbers, but it can still guarantee that any object belonging to that type does know how to generate random numbers - whatever subtype they belong to will have to define it for them. Then you can easily add various distributions like
class GaussianRNG implements MyRNG {
private final double mean;
private final double standardDeviation;
private final Random rng = new Random();
public GaussianRNG(double mean, double standardDeviation) {
this.mean = mean;
this.standardDeviation = standardDeviation;
}
@Override
public double getMyRandom() {
return standardDeviation * rng.nextGaussian() + mean;
}
}
And thanks to subtyping, you can just go MyRNG rng = new GaussianRNG(5.66, 1.2);
, even though MyRNG
has no idea GaussianRNG
exists! Need another distribution? You can just make one!
Now, you'll notice I kind of dropped the ability to pick a distribution type at runtime. But there's nothing saying you can't have that too! You're definitely allowed to have something like
public static MyRNG createRNG(DistributionType distribution, double... parameters) {
switch (distribution) {
case UNIFORM_DOUBLE:
if (parameters.length != 2) throw new IllegalArgumentException();
return new UniformDoubleRNG(parameters[0], parameters[1]);
case GAUSSIAN:
if (parameters.length != 2) throw new IllegalArgumentException();
return new GaussianRNG(parameters[0], parameters[1]);
case CHOICE:
return new ChoiceRNG(parameters);
}
}
Finally, I would like to point out a few more things about the way your current approach is implemented:
- It seems a bit strange to me that you call
parametersFromDescription
each time you fetch a random number. Wouldn't it be easier to just do that once during construction and store the parameters? That way you can even check during construction to make sure they're valid. - On a related note, the
MyRNG
constructor writes an error message toSystem.out
... but it doesn't actually tell the program that anything went wrong. It returns just fine, and whoever called it ends up with aMyRNG
object and will probably assume it's valid and usable. Instead of printing an error message (which might not be something you want to do!) you shouldthrow
an exception of some sort to let the caller know that it isn't going to get anything sensible and it should figure out a way to deal with that (which might mean just displaying an error message to the user). Same forgetMyRandom
, its job isn't to display error messages to the user, its job is to return a random number - if for some reason it can't, it should make it obvious that it failed so the caller can handle it in whatever manner is appropriate.throw
ing exceptions is a good way to do that. - I don't see why the
myNextWhatever
methods arestatic
. If they weren't, you wouldn't need to pass theRandom
as a parameter since you'd have access tothis.rng
. And perhapsthis.parameters
if you were to save the parameters as well