javacollectionsinterfacestoring-data

Java Interface for saving values in Sets or Lists and other Objects


I want to code a program, where the user can decide in what object to store values. The user can use Sets, Lists and Files(e.g. .txt, .xml). I want to write an interface, where in the end it doesn't matter which ("storing-")object the user chooses, so that I don't have to write the same methods for every decision.

How should I make an interface for that? Is the approach with the interface even suitable enough and what else do I need to do/consider?

import java.io.File;
public class StoreValues implements SaveInGeneral<SomeObject>{

      //user's decision (LinkedList, Set or File)

      if(decision == 1){
          SaveInGeneral<SomeObject> obj = new LinkedList<>();
      }
      if(decision == 2){
           SaveInGeneral<SomeObject> obj = new File();
      }
      //...

      obj.add(someObject);

}    

Solution

  • SaveInGeneral doesn't fit common naming strategies, which involve trying to name things with nouns. I'd call it Storage, for example.

    The generics doesn't seem useful here - the whole point is to abstract away what the underlying storage mechanism is. So get rid of that.

    Then, just define what, exactly, 'save an object' means. For example, a List can store items (.add(newItem)), but you can retrieve items by index (.get(5)), create an iterator (with .iterator()) so that you can for (String v : list) through it, and ask it its size (.size()), etc.

    What kind of primitives are you looking for?

    Presumably if all this does is store objects and nothing else, the one and only method you're looking for is .store(Object o).

    The problem is, the task: "Store an arbitrary object on disk" just does not work. Most objects cannot be stored to disk at all. I strongly suggest you then limit the .store() method to only allow things you know how to store. You could go with Serializable, but that is a giant can of worms (serializable is extremely convoluted), or you need to involve third party libraries such as Jackson that attempt to marshall objects into e.g. JSON or XML.

    You then need to think about the needs of your various targeted platforms (files, databases, lists, sets, etc), and cross that off vs. the needs of your code that needs to store things. Find the subset such that it consists solely of things which are feasible to implement in all targeted storage mechanisms, and which is sufficient for your code that needs a storage backend.

    This can get complicated fast. For example, when reading out JSON produced by Jackson, you need to provide which class you want to read the JSON into, which is not a thing lists need (they know which object kind they stored already). Files, in turn, don't like it if you keep writing a tiny blob of data, then close the file, then open it again - the overhead means that this:

    is literally about 1000 times slower vs:

    In other words, you'd have to update your API to involve an opening and a closing step, or accept that the file based storage backend is incredibly (1000x) slow.

    Here is the most simple take - let's store only Strings because that's easy to send to anything from DBs to files to lists to network sockets, and let's just accept an inefficient algorithm for now:

    public interface Storage {
        public void store(String data) throws IOException;
    }
    

    some implementations:

    public class ListBasedStorage implements Storage {
        private final List<String> list = new ArrayList<String>();
    
        public List<String> getBackingList() {
            return list;
        }
    
        public void store(String data) {
            list.add(data);
        }
    }
    
    public class FileBasedStorage implements Storage {
        private final Path target;
        private static final Charset CHARSET = StandardCharsets.UTF_8;
    
        public FileBasedStorage(Path p) {
            this.target = target;
        }
    
        public void store(String data) throws IOException {
            String line = data.replaceAll("\\R", " ") + "\n";
            Files.write(target, line, CHARSET, StandardOpenOption.APPEND);
        }
    }
    

    and to use this:

    public static void main(String[] args) throws Exception {
      Storage storage = new FileBasedStorage(Paths.get("mydata.txt"));
      new MyApp().sayHello(storage);
    }
    
    public void sayHello(Storage storage) throws IOException {
      storage.store("Hello!");
      storage.store("World");
    }
    

    You can then start complicating matters by adding more data types or using e.g. JSON and a JSON marshaller like jackson to turn this data into stuff you can put in a file or db, adding retrieval code (where storage can also be asked how many entries are included, and e.g. asking for an iterator to go through the data, etcetera), and adding a 2-layered approach where you ask storage for a 'session', which must be safely closed using try (Session session = storage.start()) {}), in order to have fast file and DB writes (both files and DBs are transactional, in the sense that they work far better if you explicitly start, do stuff, and then save all you just did).