'Can I create an abstraction to hide my "checking the cache" logic?

I have getResponse() methods which build a response from data fetched from the database (or pre-saved in a SavedData object). I would like to find a way to abstract out the "check savedData" logic from my getResponse() methods. Ideally, I want to figure out a way where my getResponse() methods don't even know SavedData exists, it is just hidden behind some interface. Is there a good abstraction here I can use to clean up this code?

The following is just pseudo-code. For each field which is returned in the getResponse() JSON object, they first check if that field has been saved in some SavedData and use it if it has, otherwise, they need to query the DB for the field.

interface ResponseGetter {
  public Response getResponse(String userID, SavedData savedData);
}

class A implements ResponseGetter {
  public Response getResponse(String userID, SavedData savedData) {           
      List<String> foo;
      int bar;
      String bizz;

      foo = savedData.get(userID, "foo");
      if (foo == null) {
        foo = loadFooFromDB(userID);
      }
      bar = savedData.get(userID, "bar");
      if (bar == null) {
        bar = loadBarFromDB(userID);
      }
      bizz = savedData.get(userID, "bizz");
      if (bizz == null) {
        bizz = loadBizzFromDB(userID);
      }

      JSONObject json = new JSONObject();
      json.put("foo", foo);
      json.put("bar", bar);
      json.put("bizz", biz);
      return new Response(json);
  }

  private List<String> loadFooFromDB(String userID) {
     List<String> returnList = new ArrayList<String>();
     DB db = this.getDB();
     String query = "SELECT foo FROM SomeTable WHERE user_id=" + userID;
     Results results = db.executeQuery(query);
     for (Result result : results) {
         returnList.add(result.toString());
     }

     return returnList;
  }
}

class B implements ResponseGetter {
  public Response getResponse(String userID, SavedData savedData) {           
      List<String> baz;
      int qux;
      String corge;

      baz = savedData.get(userID, "baz");
      if (baz == null) {
        baz = loadBazFromDB(userID);
      }
      qux = savedData.get(userID, "qux");
      if (qux == null) {
        qux = loadQuxFromDB(userID);
      }
      corge = savedData.get(userID, "corge");
      if (corge == null) {
        corge = loadCorgeFromDB(userID);
      }
      
    
      JSONObject json = new JSONObject();
      json.put("baz", baz);
      json.put("qux", qux);
      json.put("corge", corge);
        
      return new Response(json);
  }
}


Solution 1:[1]

I would use generics and the Strategy Design Pattern.

I didn't really know what types and classes you could give up, since you provided a pseudo-code.

If I forgot to put something importante, please comment.


But I would go with something like this:

interface ResponseGetter<R> {
R getResponse(String userId, String dataToCheck, SavedData<R> savedData, Function<String, R> lambda);
}

class DB<R> {
public R executeQuery(String query) {
    return null;
}
}

interface SavedData<R> {
    public R get(String userId, String name);
}

class SomeClass<R> implements ResponseGetter<R> {

    @Override
    public R getResponse(String userId, String dataToCheck, SavedData<R> savedData, Function<String, R> lambda) {
        R checkedData = savedData.get(userId, dataToCheck);
        return checkedData == null ? loadDataFromDB(userId, lambda) : checkedData;
    }

    public R loadDataFromDB(String userId, Function<String, R> lambda) {
        return lambda.apply(userId);
    }
}

class SavedDataImpl<R> implements SavedData<R> {

    @Override
    public R get(String userId, String name) {
        return null;
    }
}

class Main  {

    public static void main(String[] args) {

        // foo
        SomeClass<List<String>> someClass = new SomeClass<>();
        SavedDataImpl<List<String> savedDataImpl = new SavedDataImpl<>();
        DB<List<String>> db = new DB<>();

        List<String> foo = someClass.getResponse("1", "foo", savedDataImpl, (String userId) -> {
        List<String> result = db.executeQuery("SELECT foo FROM SomeTable WHERE user_id=" + userId);
        return result;
    });

    // bar
    SomeClass<Integer> someClassTwo = new SomeClass<>();
    SavedDataImpl<Integer savedDataImplTwo = new SavedDataImpl<>();
    DB<Integer> dbTwo = new DB<>();

    Integer bar = someClassTwo.getResponse("1", "bar", savedDataImplTwo, (String userId) -> {
        Integer result = dbTwo.executeQuery("SELECT bar FROM SomeTable WHERE user_id=" + userId);
        return result;
    });

    // bizz
    SomeClass<String> someClassThree = new SomeClass<>();
    SavedDataImpl<String savedDataImplThree = new SavedDataImpl<>();
    DB<String> dbThree = new DB<>();

    String bizz = someClassThree.getResponse("1", "bizz", savedDataImplThree, (String userId) -> {
        String result = dbThree.executeQuery("SELECT bizz FROM SomeTable WHERE user_id=" + userId);
        return result;
    });

    Map json = new HashMap();
    json.put("foo", foo);
    json.put("bar", bar);
    json.put("bizz", bizz);

}
}

That way you are abstracting the behavior of the how to get the data and wich data to get.

I just formatted the response in a Map for simplicity.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pedro Luiz