Question

I've the case that I have four types of data objects:

class DataTypeAlpha extends DataType
class DataTypeBeta extends DataType
class DataTypeGamma extends DataType
class DataTypeDelta extends DataType

and four different TreeNode types from the GUI framework, each one specific to the wrapped DataType:

class AlphaTreeNode extends MyAppTreeNode
...

Now I often have the pattern that I have an instance of DataType and need a new instance of MyAppTreeNode. I see two solutions. Solution one:

class DataType {
  // Instantiates and returns the appropriate MyAppTreeNode for this DataType
  abstract MyAppTreeNode createTreeNode();
}

Solution two:

class MyAppTreeNode {
  static MyAppTreeNode createTreeNodeForDataType(DataType dataType) {
    if(dataType instanceOf DataTypeAlpha) return new AlphaTreeNode((DataTypeAlpha)dataType)
    else if (dataType instanceOf DataTypeBety) return new BetaTreeNode((DataTypeBeta)dataType)
    else if ...
    else if ...
    else throw new IllegalArgumentException();
  }
}

Solution one uses polymorphism, is shorter and more "elegant". But I'd prefer that the DataType classes have no knowledge about the GUI framework that I use. Maybe I could even use two different GUI frameworks?

Do you see a third solution? I added the Guice tag to this question. Maybe there is some function in Guice or another dependency injection library that could help here?

Looking through similar questions:

  • Of course I will use the Factory Pattern for this, but inside the factory I'm still left with the question.
Était-ce utile?

La solution

You might use a visitor inspired approach for this. As usual all DataType objects has an accept method, but as opposed to the normal visitor pattern, it does not traverse children and it will return a value. To avoid too much confusion, lets call object passed to accept for an operator instead of visitor. The trick is to make accept and operators return a generic type.

So the code will be something like this in the data model

public abstract class DataType {
  public abstract <T> T accept(Operator<T> op);
}

public interface Operator<T> {
  T operateAlpha(DataTypeAlpha data);
  T operateBeta(DataTypeBeta data);
  ...
}

public class DataTypeAlpha extends DataType {
  public <T> T accept(Operator<T> op) {
    return op.operateAlpha(this);
  }
}
....

and in the GUI you will have

public class TreeNodeFactory implements Operator<MyAppTreeNode> {
    public MyAppTreeNode operateAlpha(DataTypeAlpha data) {
      return new AlphaTreeNode(data);
    }
    ...
 }

 public class MyAppTreeNode {
   static TreeNodeFactory factory = ...;
   static MyAppTreeNode createTreeNodeForDataType(DataType dataType) {
     return dataType.accept(factory);
   }       
 }

Autres conseils

So the short, simple answer is that a constructor can only return its own type. No subtypes, no other classes, no reused instances, no null—only a new instance of that type. So you're looking for a solution that operates outside the confines of a constructor here. The simplest and most common workaround is to write a static factory method (usually named newInstance or getInstance) which returns any new or existing instance of the enclosing class and can return a subclass or null without trouble.

Your points about your solution 1 and 2 are valid. It'd be great to avoid making the data types aware of the UI, and in your situation (with only four types) I'd probably opt for your solution 2. If you have operations that will vary among those types—which is a pretty common requirement in a GUI that puts a mixture of types into a tree—Bittenus's solution is probably worth it. (It's a lot of code to handle if you only need to do this sort of thing once.)

If you somehow expect your type count to grow but your operations to never grow, one alternative is to extract the polymorphic creation into a separate Factory, which might look like this:

class MyAppTreeNode {
  interface Factory {
    MyAppTreeNode create(DataType type);
  }
}

class AlphaTreeNode extends MyAppTreeNode {
  static class Factory implements MyAppTreeNode.Factory {
    @Override public AlphaTreeNode create(DataType type) {
      // Remember, in an override your return types can be more-specific
      // but your parameter types can only be less-specific
      return new AlphaTreeNode((DataTypeAlpha) type);
    }
  }
}

Then you can just make a map (though consider Guava's ImmutableMap for better semantics):

private static Map<Class<?>, MyAppTreeNode.Factory> factoryMap = new HashMap<>();
static {
  factoryMap.put(DataTypeAlpha.class, new AlphaTreeNode.Factory());
  // ...
}

public static createTreeNode(DataType type) {
  return factoryMap.get(type.getClass()).create(type);
}

More trouble than it's worth? Probably, in most cases. But bear in mind that it's probably the best that Guice can get you, as well. Guice has some ability to auto-generate the Factory implementation for you, but you'll still need to map DataType to MyAppTreeNode.Factory one way or another, and it's going to have to live in a Map, a conditional, or the double indirection that powers the Visitor pattern.

Hope this helps, if only to endorse the answers you already have!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top