Imagine some code like the following:
class Cache {
private Map<String, String> values = new HashMap<String, String>();
public String getFromCache(String key) {
if (!values.containsKey(key)) {
throw new NoEntryException();
}
return values.get(key);
}
public void setInCache(String key, String value) {
this.values.put(key, value);
}
}
class Foo {
private Cache cache = new Cache();
public String bla(String foo) {
try {
return cache.getFromCache(foo);
} catch (NoEntryException ex) {
String result = doSomeHeavyOperation(foo);
cache.setInCache(foo, result);
}
}
public String doSomeHeavyOperation(String something) {
// heavy operation
return something;
}
}
Is this a valid use case for an exception? On one hand, it might be computationally slower to throw the exception, than doing a "cacheContains(String)" method or checking for null (although in that case, does null mean "contains no element" or "always null for doSomeHeavyOperation"?). On the other hand it does affect the control flow, but it doesn't seem to do so in a way that makes it less clear to me. I'd even say it looks cleaner to me than checking up front using a cacheContains(String).
The values in the cache are always the same, for the sake of simplicity (although you can expect a cache invalidation after 30 minutes).
Of course the cache used here is quite simple, but later on will be a wrapper around a more complicated system, like a redis integration.
Edit:
Here is some example code on how to do the accepted answer in Java (needs improvement with generics, multiple arguments, etc):
abstract class MissingResultCaller {
abstract String doCall(String argument);
}
class Cache {
private Map<String, String> values = new HashMap<String, String>();
public String getFromCache(String key, MissingResultCaller missingResultCaller) {
if (!values.containsKey(key)) {
String value = missingResultCaller.doCall(key);
setInCache(key, value);
return value;
}
return values.get(key);
}
public void setInCache(String key, String value) {
this.values.put(key, value);
}
}
class Foo {
private Cache cache = new Cache();
public String bla(String foo) {
return cache.getFromCache(foo, new MissingResultCaller() {
@Override
String doCall(String argument) {
return doSomeHeavyOperation(argument);
}
});
}
public String doSomeHeavyOperation(String something) {
System.out.println("Did heavy operation");
return "heavy operation return";
}
}
Lazy
type is that the evaluator (or value supplier) is kept together - coupled - with the value container rather than being a separate entity. It may or may not be what you want, depending on the context