Reputation: 965
What I am trying to do, is implementig a clickstream data generator, that can be extended in a nice way.
What I was thinking of:
The properties are Classes, e.g. each user has some kind of browser. So there is a browser class. Same for language, plugins etc....
For each Property there is a Factory that creates me a random instance of a Property e.g. new Browser("Firefox") or new Language("German"). The possible values are stored in a file for each property.
Basically all these factories and property classes are doing the same thing. Right now i have a seperate factory for each property and for each new porperty i have to generate a new factory.
My Question is, is there a possibility to implement some kind of generic factory for all the properties i have, and for new ones to come.
This is my code:
public abstract class Property {
protected String value;
Random rand;
public Property(String value) {
this.rand = new Random();
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
public class Browser extends Property{
public Browser(String value) {
super(value);
}
}
public abstract class AbstractFactory implements IFactory{
List<String> valuesList;
FileReader fileReader;
BufferedReader bufferedReader;
Random rand;
public AbstractFactory(String inputFile) {
rand = new Random();
this.valuesList = new LinkedList<String>();
String line = null;
try {
fileReader = new FileReader(inputFile);
bufferedReader = new BufferedReader(fileReader);
while ((line = bufferedReader.readLine()) != null) {
valuesList.add(line);
}
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import model.Browser;
public class BrowserFactory extends AbstractFactory{
public BrowserFactory(String inputFile) {
super(inputFile);
}
@Override
public Browser getInstance() {
return new Browser(valuesList.get(rand.nextInt(valuesList.size())));
}
}
Upvotes: 2
Views: 1986
Reputation: 44328
Hungarian notation (technically, systems Hungarian notation) is frowned upon. Java interfaces should not be named with an I
prefix. If you look at the Java SE documentation you will see that there is not a single interface named that way.
So, with that in mind, you would define Factory
as:
public interface Factory<T> {
T getInstance();
}
Then AbstractFactory copies that:
public abstract class AbstractFactory<T> implements Factory<T> {
Finally, BrowserFactory can simply make the generic type more specific:
public class BrowserFactory extends AbstractFactory<Browser> {
@Override
public Browser getInstance() {
If you want to make a single concrete class, you’ll need some uniform way of creating the classes. If they all have constructors which take a String, you can use reflection:
public class FeatureFactory<T> extends AbstractFactory<T> {
private final Constructor<T> constructor;
public FeatureFactory(Class<T> featureType) {
try {
this.constructor = featureType.getConstructor(String.class);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException(
"Cannot find/access (String) constructor in " + featureType, e);
}
}
@Override
public T getInstance() {
try {
return constructor.newInstance(
valuesList.get(rand.nextInt(valuesList.size())));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
You must pass a Class object for reflection to work. Generics are erased at runtime, so you cannot derive the class from just <T>
. In fact, you’ll find that Java SE classes work the same way; see EnumSet and EnumMap for examples.
Another somewhat cleaner approach is using a Java 8 Function:
public class FeatureFactory<T> extends AbstractFactory<T> {
private final Function<String, ? extends T> creator;
public FeatureFactory(Function<String, ? extends T> creator) {
this.creator = Objects.requireNonNull(creator,
"Creation function cannot be null");
}
@Override
public T getInstance() {
creator.apply(
valuesList.get(rand.nextInt(valuesList.size())));
}
}
This could be invoked with something like:
FeatureFactory<Browser> browserFactory = new FeatureFactory<>(Browser::new);
Upvotes: 1