Proxy Pattern: Caching Proxy
Problem
A slow service has the method compute(key) that returns the sum of the character codes of the key modulo 1000. Build a caching proxy with the same method. The proxy asks the real service only the first time it sees a key and remembers the answer. Commands: get key prints key: value (miss) when the real service was called, or key: value (hit) when the proxy answered from its cache. stats prints service calls=N cache hits=M. The program uses the proxy through the same interface as the real service.
Constraints
- 1 <= commands <= 10^4
- keys have no spaces
Examples
Input
get abc get abc stats
Output
abc: 294 (miss) abc: 294 (hit) service calls=1 cache hits=1
Solutions
A proxy has the same interface as the object it protects, so it can add caching, access checks, logging or lazy loading without changing the caller or the real class. Remember that cached answers can become stale when the real data changes.
/*
* Problem : Process lines.
* Input : Several lines.
* Output : One answer per command.
* Approach: A proxy in front of a service.
* Time : O(n)
* Space : O(n)
* Java : 8 or newer.
*/
import java.util.Scanner;
public class Main {
// Step 1: Both the real service and the proxy implement the same interface. The caller cannot tell them apart.
interface Service {
int compute(String key);
}
static class RealService implements Service {
int calls = 0;
@Override
public int compute(String key) {
calls++;
int sum = 0;
for (char c : key.toCharArray()) {
sum += c;
}
return sum % 1000;
}
}
// Step 2: The proxy keeps a map of earlier answers and calls the real service only for new keys.
static class CachingProxy implements Service {
private final Service real;
private final java.util.Map<String, Integer> cache = new java.util.HashMap<>();
int hits = 0;
boolean lastWasHit = false;
CachingProxy(Service real) {
this.real = real;
}
@Override
public int compute(String key) {
lastWasHit = cache.containsKey(key);
if (lastWasHit) {
hits++;
return cache.get(key);
}
int value = real.compute(key);
cache.put(key, value);
return value;
}
}
// This method solves the task. It receives the input and returns the answer.
static String[] solve(String[] lines) {
RealService real = new RealService();
CachingProxy proxy = new CachingProxy(real);
String[] output = new String[lines.length];
for (int i = 0; i < lines.length; i++) {
String[] part = lines[i].split(" ");
// Step 3: The main loop talks only to the proxy.
if (part[0].equals("get")) {
int value = proxy.compute(part[1]);
output[i] = part[1] + ": " + value + (proxy.lastWasHit ? " (hit)" : " (miss)");
} else {
output[i] = "service calls=" + real.calls + " cache hits=" + proxy.hits;
}
}
return output;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
java.util.List<String> all = new java.util.ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (!line.isEmpty()) {
all.add(line);
}
}
String[] lines = all.toArray(new String[0]);
System.out.println(String.join("\n", solve(lines)));
}
}
Hints (1)
- What can go wrong when cached data changes?