简体   繁体   中英

Java Generic Factory pattern implementation

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, eg 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 eg 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())));
}

}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM