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

Best approachTime O(n)Space O(n)

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.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * 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)
  1. What can go wrong when cached data changes?