Search code examples
javagenericsintellij-ideaabstract-factory

Java dealing with a lot of concrete factories


I want to generalize a repetitive piece of Java code for a lot of (~40-50) similar entities (in my case, this piece is indexing of files with these entities).

I tried to refactor it with generic method, but, as a result, I get a constructor of generic class that is apparently prohibited in Java. To avoid this, I implemented abstract factory pattern and here's what I've get.

public <E extends CMObject, F extends IndexedFile<E>> F indexFile(CMFactory<E, F> factory) {
    F items;
    ByteBuffer[] buffs;

    // ...filling buffers...

    items = factory.makeFile(buffs); // as I cannot do items = new F(buffs)

    return items;
}

public CityFile getCities() {
    return indexFile(new CityFactory());
}

public ContinentFile getContinents() {
    return indexFile(new ContinentFactory());
}
// a lot of more

This solves an issue of creating an instance of generic class. However, I now face a task of creating a concrete factory for each single entity that seems to be a lot of monotonous work as they all look like each other.

public abstract class CMFactory<E extends CMObject, F extends IndexedFile<E>> {
    public abstract F makeFile(ByteBuffer[] buff);
}

public class CityFactory extends CMFactory<City, CityFile> {
    @Override
    public CityFile makeFile(ByteBuffer[] buff) {
        return new CityFile(buff);
    }
}
public class ContinentFactory extends CMFactory<Continent, ContinentFile> {
    @Override
    public ContinentFile makeFile(ByteBuffer[] buffs) {
        return new ContinentFile(buffs);
    }
}

The question is: is there any way to automatize creation of such factories? Or maybe is there another pattern that can at least make such creation less painful?

I tried to use IntelliJ IDEA's Replace Constructor with Factory Method refactor, but it didn't help me.


Solution

  • Since your CMFactory is almost a functional interface you can use constructor handles instead of implementing CMFactory for each concrete class:

    Make CMFactory an interface:

    public interface CMFactory<E extends CMObject, F extends IndexedFile<E>> {
        public abstract F makeFile(ByteBuffer[] buff);
    }
    

    and then write

    public CityFile getCities() {
        return indexFile(CityFile::new);
    }
    

    You can even discard CMFactory and use java.util.Function:

    public <E extends CMObject, F extends IndexedFile<E>> F indexFile(Function<ByteBuffer[],F> factory) {
        ByteBuffer[] buffs;
        // ...filling buffers...
        return factory.apply(buffs);
    }