2

I have a Parent interface.

public interface Parent{...}

And two classes that implement this interface.

public class ChildA implements Parent {...}
public class ChildB implements Parent {...}

Now I want to create an object of the child based on the parameter passed. Something like this,

Parent instance1 = Parent.getChild("Key1");

Currently, I am achieving this by using hashmaps.

static Map<String,Class<?>> map = new HashMap<String,Class<?>>(); 
    
static void init() throws ClassNotFoundException {
    map.put("Key1",Class.forName("ChildA"));
    map.put("Key2",Class.forName("ChildB"));
}
    
static Parent getChild(String key) throws InstantiationException, IllegalAccessException {
    return (Parent) map.get(key).newInstance();
}

But the issue here is every time I implement a new child I will have to add it in the Parent init method. So is there any cleaner way to do this? Something like adding the information of the key to the child itself,

public class ChildA implements Parent {
    
    private String key = "Key1";
    ...

So that when from the parent class I call getChild it refers to the respective Child.

Basically I am asking for a way to dynamically refer to a child of the parent based on the parameter passed.

  • I think you are talking about Strategy Pattern , check here https://www.journaldev.com/1754/strategy-design-pattern-in-java-example-tutorial – Harmandeep Singh Kalsi Jul 06 '20 at 08:42
  • I think you can use class names as a key, and when you put into map you can use getClass().getSimpleName(),i.e. you should automate the key values somehow and mine is just an idea. – Y.Kakdas Jul 06 '20 at 08:42
  • try this, add your key name constraint https://stackoverflow.com/questions/3123095/how-can-i-find-all-implementations-of-interface-in-classpath – Sabareesh Muralidharan Jul 06 '20 at 08:47
  • See [java - Automatic factory registration - Stack Overflow](https://stackoverflow.com/questions/7619153/automatic-factory-registration). –  Jul 06 '20 at 15:51

2 Answers2

4

You can use the service provider mechanism. This works best when actively using Java’s module system as then, the necessary parts are even integrated into the Java language.

The module containing the Parent interface must export its package, if implementations in other modules should be possible. Then, a module which wants to look up implementations of the interface must have a

uses yourpackage.Parent;

directive in its module-info. A module providing implementations must have a directive like

provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;

in its module-info. It’s possible to use the interface and provide implementations within the same module. This might also be the module containing the declaration of the interface. In that case, it’s even possible to use the mechanism within a single module only and not to export the interface at all.

The compiler will already check that the specified implementation classes are public, have a public no-arg constructor, and truly implement the service interface. The implementations do not need to reside in an exported package; it’s possible that the interface is the only way to access an implementation from another module.

Since the compiler pre-checks the required invariants, you don’t need to deal with the issues inherent to Reflection. For example, you don’t need to declare or handle InstantiationException or IllegalAccessException.

A simple setup could be

module-info
module ExampleApp {
    uses yourpackage.Parent;
    provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
}
yourpackage/Parent
package yourpackage;

public interface Parent {
}

yourpackage/TheKey
package yourpackage;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheKey {
    String value();
}
yourpackage/ChildA
package yourpackage;

@TheKey("Key1")
public class ChildA implements Parent {
}
yourpackage/ChildB
package yourpackage;

@TheKey("Key2")
public class ChildB implements Parent {
}
independentpackage/UsingTheInterfaces
package independentpackage;

import java.util.ServiceLoader;
import yourpackage.Parent;
import yourpackage.TheKey;

public class UsingTheInterfaces {
    public static void main(String[] args) {
        Parent p = getChild("Key1");
        System.out.println(p);
    }
    static Parent getChild(String key) {
        return ServiceLoader.load(Parent.class).stream()
            .filter(p -> {
                TheKey keyAnno = p.type().getAnnotation(TheKey.class);
                return keyAnno != null && keyAnno.value().equals(key);
            })
            .findAny()
            .map(ServiceLoader.Provider::get)
            .orElse(null); // decide how to handle absent keys
    }
}

As said, when you have an exports yourpackage; directive, implementations can be provided by other modules and the ServiceLoader will discover those implementations dynamically for all modules present at runtime. The using module does not need to know them at compile time.

There is a predecessor mechanism for older Java versions not having a module system, which is also handled by ServiceLoader in a backwards compatible manner. Those providers have to be packaged in a jar file containing a file META-INF/services/yourpackage.Parent containing a list of all classes within the jar file implementing the interface. Before Java 9, the ServiceLoader also lacks the Stream API support that allowed to query the annotations before instantiating the implementation. You can only use the Iterator that will already instantiate the classes and return an instance.

One way to avoid unnecessary overhead with the old API, is to split the interface into a service provider interface and an actual service interface. The provider interface will supply meta information and act as a factory for actual service implementations. Compare with the relationship between CharsetProvider and Charset or FileSystemProvider and FileSystem. These examples also demonstrate that the service provider mechanism is already widely used. More examples can be found in the java.base module documentation.

This was just an overview; the API documentation of ServiceLoader contains more details about the mechanism.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

Option 1: I suggest you to use a simple factory class like this one:

public class ChildFactory {
    public static Parent getChild(String key) throws ClassNotFoundException {
         if (key.equals("Key1"))
             return new ChildA();
         if (key.equals("key2"))
             return new ChildB();
         throw new ClassNotFoundException();
    }
}

These are the reasons:

  1. Simple and easy to extend: the class does only one thing, create objects
  2. Don't use the deprecated newInstance()
  3. Clean the interface Parent from static "factory" methods

Option 2: define a function that given the key it returns the class name and use it to create the instance:

public class ChildFactory {
    public static Parent getChild(String key) {
        String className = childClassNameFor(key);
        return (Parent) Class.forName("my.package."+className).newInstance();
    }
    private static String childClassNameFor(String key) {
        return "Child" + key;
    }
}

And then you can use it like this:

Parent p = ChildFactory.getChild("A");
Marc
  • 2,738
  • 1
  • 17
  • 21
  • Yes Factory pattern is good but still his question was not solve. When new child class added The child factory need to be change. how to solve that – janith1024 Jul 06 '20 at 11:03