Test Double: a Stub Rate Provider

medium Testing and Clean Code

Problem

A CurrencyConverter gets the rate from a RateProvider. In a test, the provider is a stub: it gives fixed answers and calls no real service. Commands: rate CUR VALUE (the stub now answers VALUE for CUR; a new value replaces the old one), and convert CUR AMOUNT (print AMOUNT times the rate, or no rate for CUR if the stub has no answer).

Constraints

  • 1 <= commands <= 100
  • VALUE and AMOUNT are whole numbers

Examples

Input

rate USD 83
convert USD 10
convert EUR 5

Output

830
no rate for EUR

Solutions

Best approachTime O(n)Space O(n)

Because the converter gets its provider from outside, the test can give it a stub with fixed rates. The test needs no network, runs fast and always gives the same result.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * Problem : Convert with a stub.
 * Input   : Command lines.
 * Output  : One line per convert.
 * Approach: Stub provider.
 * Time    : O(n)
 * Space   : O(n)
 * Java    : 8 or newer.
 */
import java.util.Scanner;

public class Main {
        // Step 1: The converter depends on this interface. A real provider would call a web service.
        interface RateProvider {
            Long rateOf(String currency);
        }

        // Step 2: The stub gives fixed answers that the test has set.
        static class StubRateProvider implements RateProvider {
            final java.util.Map<String, Long> rates = new java.util.HashMap<>();

            @Override
            public Long rateOf(String currency) {
                return rates.get(currency);
            }
        }

        // Step 3: The converter does the work and does not know which provider it has.
        static class CurrencyConverter {
            private final RateProvider provider;

            CurrencyConverter(RateProvider provider) {
                this.provider = provider;
            }

            String convert(String currency, long amount) {
                Long rate = provider.rateOf(currency);
                return rate == null ? "no rate for " + currency : String.valueOf(rate * amount);
            }
        }

    // This method solves the task. It receives the input and returns the answer.
    static String[] solve(String[] lines) {
        StubRateProvider stub = new StubRateProvider();
        CurrencyConverter converter = new CurrencyConverter(stub);
        java.util.List<String> out = new java.util.ArrayList<String>();
        for (String line : lines) {
            String[] part = line.split(" ");
            if (part[0].equals("rate")) {
                stub.rates.put(part[1], Long.parseLong(part[2]));
            } else {
                out.add(converter.convert(part[1], Long.parseLong(part[2])));
            }
        }
        return out.toArray(new String[0]);
    }

    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 (2)
  1. What does the stub replace?
  2. Why does the converter not create the provider itself?