Polymorphic factory / getInstance() in Java

16,301

Solution 1

The static method is defined on the parent class, and it's called statically as well. So, there's no way of knowing in the method that you've called it on the subclass. The java compiler probably even resolves the call statically to a call to the parent class.

So you will need to either reimplement the static method in your child classes as you propose, or make them not static so you can inheritance (on a hierarchy of factory objects, not classes), or pass a parameter to signify the type you want to create.

Check out the EnumSet.noneOf() method. It has a similar issue as you do, and it solves it by passing the java.lang.Class method. You could use newInstance on the class. But personally, I'd just use factory objects rather than classes with static methods.

Solution 2

Have you looked into Guice? Not sure if it would solve your problem exactly, but it acts as a generic factory and dependency injection container, and eliminates non-type safe String keys.

Solution 3

after reading you explanation of the problem i think your making it very difficult for yourself by sub classing in your graph construction. i think the problem becomes much more simple if you separate the dependency graph from the "program information"

use an Interface such as:

Public Interface Node<T> {
  public Object<T>    getObject();
  public String       getKey();
  public List<String> getDependencies();
  public void         addDependence(String dep);
}

and then use a factory to instantiate you nodes

Solution 4

The Class class is parameterized with the type of instance it can create. (ex: Class<String> is a factory for String instances.)

I don't see any way to get around knowing the type of instance that should be created when you getOrCreate using the factory method here, so I would recommend passing it to the method and parameterizing on the type being generated:

private static Map<String, MyClass> directory = new HashMap<String, MyClass>();

public static <T extends MyClass> T getInstance(String name, Class<T> generatorClass)
{
  MyClass node = directory.get(name);    
  if(node == null) {
    node = generatorClass.getConstructor(String.class).newInstance(name);
    directory.put(name, node);
  }
  return node;
}

Also, I noticed you weren't actually putting the newly constructed nodes in the directory - I'm assuming that's an oversight. You could also overload this method with another that didn't take a generator and hardcoded to the default type:

public static MyClass getInstance(String name) {
  return getInstance(name, MyClass.class);
}

Solution 5

You seem to imply that somewhere you know what class it should be if it doesn't exist. If you implement that logic in your factory you should get the correct classes.

This way you should also have no need to know what class actually was returned from the factory.

I would also likely make 'MyClass' an interface if you're considering a Factory pattern.

Share:
16,301

Related videos on Youtube

Tomato
Author by

Tomato

I'm a senior software engineer at a FinTech startup working primarily on robo- pension advice. I've previously worked on consumer energy supplier's software backend, and on B2B messaging. My current work mostly uses Node.js, given an FP flavour by heavy use of Ramda. My previous jobs gave me detailed knowledge of Java, C, network protocols, databases, queues; lots of things.

Updated on April 19, 2022

Comments

  • Tomato
    Tomato about 2 years

    I'm aiming to create a set of objects, each of which has a unique identifier. If an object already exists with that identifier, I want to use the existing object. Otherwise I want to create a new one. I'm trying not to use the word Singleton, because I know it's a dirty word here...

    I can use a factory method:

        // A map of existing nodes, for getInstance.
    private static Map<String, MyClass> directory = new HashMap<String, MyClass>();
    
    public static MyClass getInstance(String name) {
        MyClass node = directory.get(name);
        if(node == null) {
           node == new MyClass(name);
        }
        return node;
    }
    

    Or equally, I could have a separate MyClassFactory method.

    But I had intended to subclass MyClass:

    public class MySubClass extends MyClass;
    

    If I do no more, and invoke MySubClass.getInstance():

    MyClass subclassObj = MySubClass.getInstance("new name");
    

    ... then subclassObj will be a plain MyClass, not a MySubClass.

    Yet overriding getInstance() in every subclass seems hacky.

    Is there a neat solution I'm missing?


    That's the generalised version of the question. More specifics, since the answerers asked for them.

    The program is for generating a directed graph of dependencies between nodes representing pieces of software. Subclasses include Java programs, Web Services, Stored SQL procedures, message-driven triggers, etc.

    So each class "is-a" element in this network, and has methods to navigate and modify dependency relationships with other nodes. The difference between the subclasses will be the implementation of the populate() method used to set up the object from the appropriate source.

    Let's say the node named 'login.java' learns that it has a dependency on 'checkpasswd.sqlpl':

    this.addDependency( NodeFactory.getInstance("checkpasswd.sqlpl"));
    

    The issue is that the checkpasswd.sqlpl object may or may not already exist at this time.

  • Tomato
    Tomato over 15 years
    I'm struggling to see how either of these help. Can you expand on it?
  • Ken Gentle
    Ken Gentle over 15 years
    Fair enough - perhaps if you elaborate a bit on what you're trying to accomplish I can either give you a better answer or delete it. Like what does MySubClass add to MyClass?
  • xtofl
    xtofl over 15 years
    the 'probably' made me suspicous. Appears you're wrong. The compiler looks at the declared type of an instance to find the actual static method to call. This is called 'hiding' as opposed to 'overriding'. (-1)
  • Tomato
    Tomato over 15 years
    OK thanks. I tried to generalise in order not to bog the question down in specifics. But I'll edit it to add those specifics now.
  • Tomato
    Tomato over 15 years
    +1 because it matches the decision I came to after reading everyone else's good suggestions.
  • brady
    brady over 15 years
    Good suggestion. Most Dependency Injection/Inversion of Control containers do exactly what you are looking for.