I am trying to implement Factory design patter, without using reflection. The goal is, as and when subclasses of Product
are added to the project, their instances should be available in the objectPool.
The code is as below : Factory Class
public abstract class ProductFactory {
protected static final Map<String, Supplier<Product>> productSingletonObjPool = new HashMap<>();
static {
/*
//registering the sub_class-types statically. This obviously works fine.
productSingletonObjPool.put(ProductOne.class.getSimpleName(), ProductOne::getInstance);
productSingletonObjPool.put(ProductTwo.class.getSimpleName(), ProductTwo::getInstance);
*/
}
public static Map<String, Supplier<Product>> getProductSingletonObjPool() {
return productSingletonObjPool;
}
public static void register(Class type, Supplier<Product> supplier) {
productSingletonObjPool.put(type.getSimpleName(), supplier);
}
protected static Product getInstanceOfType(String type) {
Supplier<Product> productSupplier = productSingletonObjPool.get(type);
if (productSupplier != null) return productSupplier.get();
throw new RuntimeException("No such product with name " type " exists");
}
}
Abstract Product Class
public abstract class Product {
protected static void register(Class type, Supplier<Product> supplier) {
if (!ProductFactory.getProductSingletonObjPool().containsKey(type.getSimpleName())) {
ProductFactory.register(type, supplier);
}
}
}
Concrete ProductOne
public class ProductOne extends Product {
static {
Product.register(ProductOne.class, ProductOne::new);
}
private ProductOne() {}
protected static Product getInstance() {
return new ProductOne();
}
}
Concreate ProductTwo
public class ProductTwo extends Product {
static {
Product.register(ProductTwo.class, ProductTwo::new);
}
private ProductTwo() {}
protected static Product getInstance() {
return new ProductTwo();
}
}
Test Class
@Test
public void testFactory() {
Product one = ProductFactory.getInstanceOfType("ProductOne");
System.out.println("Product One : " one);
Product two = ProductFactory.getInstanceOfType("ProductTwo");
System.out.println("Product Two: " two);
}
Problem : the Map always gets populated with ProductOne. This behaviour is same as captured in this thread: Fill a hashmap in superclass from subclass constructor in Java However, My code is different than the code from this thread, though it encounters the behavior mentioned in there i.e. map always getting populated with one Supplier (in my case, its ProductOne::new).
I would like to know :
- The reason behind this behavior
- Any solution for this?
I believe the static blocks get executed when classes are loaded. I expected that static blocks of both ProductOne and ProductTwo be executed and have the productSingletonObjPool map populated with their Suppliers. This didn't happen though...map only got populated with ProductOne's supplier, as if, JVM only looked for 'a' class that extended the abstract Product class and then stopped loading additional classes which extend Product class. I also tried to debug this, but breakpoints on static blocks are skipped by debugger.
I looked into these threads, as prompted by stackoverflow, but didn't find solution to my problem :
- C# class factory with subclasses
- Factory implemented with static method
- Behavior of static blocks with inheritance
CodePudding user response:
The problem is likely that the subclass has not been initialized or probably even loaded, so the static initializer has not run, and the subclass has not been registered.
Typically, the static initializer will not run until the first invocation of either a static method or constructor. (Referencing the Class<?>
object will load but not initialize the class.) I suspect you had some other code not shown here that triggered the static initializer for ProductOne
.
Here's one solution, assuming that you're OK using a Class<?>
as the lookup key instead of a String
.
Product.java
public abstract class Product {
private static Map<Class<? extends Product>, Product> instances = new ConcurrentHashMap<>();
/** Gets the singleton instance for the provided product type. */
public static Product instanceOfType(Class<? extends Product> productType) {
ensureInitialized(productType);
Product instance = instances.get(productType);
return Objects.requireNonNull(instance, productType.getCanonicalName());
}
public abstract String getName();
/**
* Registers the singleton instance for the provided product type.
*
* <p>Each concrete subclass should invoke this method in its static initializer.</p>
*/
protected static <P extends Product> void register(Class<P> productType, P product) {
instances.put(productType, product);
}
/** Ensures that a class has been statically initialized. */
private static void ensureInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getCanonicalName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e); // unexpected
}
}
}
ProductOne.java
public final class ProductOne extends Product {
static {
Product.register(ProductOne.class, new ProductOne());
}
private ProductOne() {}
@Override
public String getName() {
return "one";
}
}
Main.java
public class Main {
public static void main(String[] args) {
Product productOne = Product.instanceOfType(ProductOne.class);
System.out.println(productOne.getName());
}
// static class
private Main() {}
}