1

I have an interface to represent a data structure with competing implementations. I need to use this in a class while decoupling the class from having to know the underlying data structure. And within this class, I will need to create several instances of this implementation. How does one do it using interface injection?

class Foo {
  Map<String, IDataStructure> map = new HashMap<String, IDataStructure>();

  public void addValue(String key, String value) {
        if(!map.containsKey(key)) {
             map.put(key, new SomeDataStructure(value));
        }
  }

}

EDIT

I found out an approach to use interface injection. Create a factory interface

class ADataStructureFactory implements DataStructureFactory {
     IDataStructure create() {
         return new SomeDataStructure();
 }
}

And inject this in the constructor

Foo(DataStuctureFactory factory)

Change the add method

public void addValue(String key, String value) {
     if(!map.containsKey(key)) {
           map.put(key, factory.create());
     }
}
user592748
  • 1,194
  • 3
  • 21
  • 45
  • 1
    "And within this class, I will need to create several instances of this implementation". Can you elaborate on this with an example? – Chetan Kinger Feb 24 '15 at 10:32
  • I still don't understand the question. There is nothing wrong in what you have done.. – Chetan Kinger Feb 24 '15 at 10:41
  • but `Foo` shouldn't know which datastructure is being used – user592748 Feb 24 '15 at 10:42
  • See my answer. Btw your code won't compile.I have edited it to complile. – Chetan Kinger Feb 24 '15 at 10:57
  • Your edit doesn't make much sense. Why go through the trouble of creating a factory when it can return only one subtype at any given time? Why use a Map in Foo. Why not just say IDataStructure dataStructure = new SomeDataStructure in the Foo class and use it directly in the addValue method? Also, what's the point of the value parameter if it won't be used? Try understanding my answer. – Chetan Kinger Feb 24 '15 at 15:16
  • I want to decouple `Foo` from the implementation of the data structure. Foo shouldn't know what kind of Data structure it is using. Doing so allows me to change the underlying data structure. – user592748 Feb 24 '15 at 17:46
  • But I will need a new instance of `IDataStructure` each time I add a key to the map. For that, I can pass dummy object that implements IDataStructure in the constructor and use `IDataStructure.getClass().newInstance()` or use an abstract factory. And looks like it is a design pattern and is cleaner than the former. – user592748 Feb 24 '15 at 18:00
  • your technique works when you know the set of keys before hand which is not the case. At any point a new key can be introduced and that should not throw an exception. – user592748 Feb 24 '15 at 18:06
  • Can you elaborate on why the edited solution cannot create new instances on the fly? It just did and I have tested it. Furthermore, when you have a method `add(String key, String value)`, the assumption is that you can add any key and any value. There is no mention of predetermined keys and hence I think that requirement was clear. – user592748 Feb 24 '15 at 18:20
  • Yes, the requirement was only to decouple `Foo` from knowing what data structure it is using. If I want another data structure, I instantiate `Foo` with another factory without having to change anything inside `Foo`. – user592748 Feb 24 '15 at 18:27
  • No. I will use one datastructure for all keys at a single time. If I want to switch at a later point to another data structure, I do so by supplying another factory. And I cannot do it upfront since I do not know what will be added. – user592748 Feb 24 '15 at 18:36
  • I will have multiple values but of the same data structure. So I won't have key1 having a Set while key2 having a list. That is what I meant. – user592748 Feb 24 '15 at 18:41

2 Answers2

1

This is what you can do :

Define an add method in IDataStructure :

public interface IDataStructure {
    public void add(String value);
}

Create an implementation of IDataStrucutre called ListDataStructure as follows :

public class ListDataStructure implements IDataStructure {

    private List<String> dataStructure = new ArrayList<String>();
    @Override
    public void add(String value) {
        dataStructure.add(value);
    }

}

Create an implementation of IDataStructure called SetDataStructure

public class SetDataStructure implements IDataStructure {

    private Set<String> dataStructure = new HashSet<String>();

    @Override
    public void add(String value) {
        dataStructure.add(value);
    }

}

Modify your Foo class as follows :

class Foo {
  private Map<String, IDataStructure> map;

  public Foo(Map<String,IDataStructure> map) {
      this.map = map;
  }

  public void addValue(String key, String value) {
        if(map.containsKey(key)) {
             map.get(key).add(value);
        } else {
           /*handle what happens when data structure does not exist. Maybe thow an exception            
        }
  }

}

Example of how to inject the supported data structures. Note that you cannot define data structures on the fly. You need to prepopulate your map in Foo with the supported implementations of data structures.

public class DataStructureExample {
    public static void main(String []args) {
        Map<String,IDataStructure> dataStrucures = new HashMap<String,IDataStructure>();

        //injecting different data structures into Foo
        dataStrucures.put("List", new ListDataStructure());
        dataStrucures.put("Set", new SetDataStructure());

        Foo foo = new Foo(dataStrucures);

        //add some value to a list data structure
        foo.addValue("List", "Value1");

        //add some valu to a set data structure
        foo.addValue("Set", "Value1");

    }
}
Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
  • but `map.get(key)` would return null, wouldn't it? What point does the injection happen? – user592748 Feb 24 '15 at 10:58
  • No. map.getKey won't return null because containsKey already checks if the key is present. Your Map needs to be pre-populated with instances. Let's say you want to support "List" and "Tree". Then you need to create these instances and populate them into the map on startup so that when the addValue method is called with "List" or "Tree" parameters, the appropriate data structure is instantiated – Chetan Kinger Feb 24 '15 at 11:00
  • Let me know if you have any other doubts. – Chetan Kinger Feb 24 '15 at 11:06
  • Can you elaborate on that step of the injection? – user592748 Feb 24 '15 at 11:08
  • Edited the answer. Note that Foo class checks for map.contains instead of !map.contains. As explained, you can't create data strucutre implementations on the fly. You need to prepulate the Map. Adding implementations to the Map is indeed where the interface injection happens. Does this answer your question now? – Chetan Kinger Feb 24 '15 at 11:26
  • Do you need any more clarifications? I think I have answered your question "but Foo shouldn't know which datastructure is being used" – Chetan Kinger Feb 24 '15 at 12:04
  • I found a method to decouple it. Thanks for your answer though. – user592748 Feb 24 '15 at 14:43
  • I saw your edit. Ideally, you should post it as an answer. Btw your implementation may be decoupled, but you are tied to one implementation of IDataStructure. You might as well just hardcode that into your Foo class by saying IData Structure dataStructure=new SomeDataStructure. – Chetan Kinger Feb 24 '15 at 15:09
0

I found out an approach to use interface injection. Create an abstract factory.

class ADataStructureFactory implements DataStructureFactory {
     IDataStructure create() {
         return new SomeDataStructure();
 }
}

And inject this in the constructor

Foo(DataStuctureFactory factory)

Change the add method

public void addValue(String key, String value) {
     if(!map.containsKey(key)) {
           map.put(key, factory.create());
     }
}
user592748
  • 1,194
  • 3
  • 21
  • 45