# Demo script — printSalesForecasts This is what I demo in the Split Loop with Lambda learning hour. I demo on `DailyForecasts.printSalesForecasts()` — the messier sibling of the method the pairs will refactor in the exercise. Same pattern, but this loop has three unwanted dependencies: `Scorer` (with a static field for extra credit), `System.out`, and the clock. I remove the clock before we start. The rest stays in. Ten minutes, six steps. ## Before we start (2 min) Strip the two header lines: ```java // var formatter = DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneId.from(ZoneOffset.UTC)); // System.out.println("Forecast at: " + formatter.format(Instant.now())); ``` I say this out loud while I delete them: *the clock is a third dependency, and we do one split at a time.* Then add the test from step 1 and run it once, so the flakiness is a memory before I start typing. --- ## Step 0 — count the concerns ![](svg/d0-start.svg) Mutation and value read, a conditional, IO — in twelve lines. I ask: *what does this loop need from the outside?* Answer: *given a name, what is the sales score?* That question is the lambda we are about to extract. The rest — printing, the clock — is not the loop's business. --- ## Step 1 — an approval test: red & flaky ![](svg/d1-test.svg) ```java @Test void salesForecasts() { var out = new ByteArrayOutputStream(); var originalOut = System.out; System.setOut(new PrintStream(out)); try { new DailyForecasts().printSalesForecasts(); } finally { System.setOut(originalOut); } Approvals.verify(out.toString()); } ``` Run it. Red, and every run disagrees with the last. Why: `Scorer` re-rolls `flavour` with a `Random`. Same situation as the exercise method — that is the point of demoing this one. --- ## Step 2 — extract the dependency-using code into a lambda ![](svg/d2-extract-lambda.svg) **After:** ```java public void printSalesForecasts() { var names = List.of("Steve", "Julie", "Francis"); Function nameToSalesScore = name -> { // ← NEW: the lambda... if (Objects.equals(name, "Steve")) { Scorer.flavour = IceCream.Strawberry; // ← ...holds BOTH scorer touches } else { scorer.updateSelection(); } return scorer.getSalesForecast(); }; for (String name : names) { System.out.println(name + " score: " + nameToSalesScore.apply(name)); // ← invocation replaced getSalesForecast } } ``` I say: *watch what goes inside — all of it. The static write too. If you leave the `Scorer.flavour = ...` behind in the loop, the extraction later drags the whole class in with it.* (The exercise pairs will meet this as the drift; here I show it before it can happen.) **Checkpoint:** compiles; behaviour unchanged; test still red & flaky. Also true and worth saying: *this lambda is not pure. The unwanted dependency lives inside it — that is exactly what we want. It is an adapter.* --- ## Step 3 — slide the lambda definition to the top of the method ![](svg/d3-slide.svg) **After:** ```java public void printSalesForecasts() { Function nameToSalesScore = name -> { // ← SLID to the top... if (Objects.equals(name, "Steve")) { Scorer.flavour = IceCream.Strawberry; } else { scorer.updateSelection(); } return scorer.getSalesForecast(); }; var names = List.of("Steve", "Julie", "Francis"); for (String name : names) { System.out.println(name + " score: " + nameToSalesScore.apply(name)); // ← ...invocation stays here } } ``` Slide, don't move. Watch the invocation — it stays exactly where the code used to be. **Checkpoint:** compiles; test still red & flaky (unchanged behaviour). --- ## Step 4 — extract the loop; the lambda becomes a parameter ![](svg/d4-extract.svg) Select from the loop through its closing brace, Extract Method. **After:** ```java public void printSalesForecasts() { Function nameToSalesScore = name -> { if (Objects.equals(name, "Steve")) { Scorer.flavour = IceCream.Strawberry; } else { scorer.updateSelection(); } return scorer.getSalesForecast(); }; var names = List.of("Steve", "Julie", "Francis"); var scores = collectSalesScores(names, nameToSalesScore); // ← EXTRACTED for (var entry : scores.entrySet()) { System.out.println(entry.getKey() + " score: " + entry.getValue()); } } private static LinkedHashMap collectSalesScores( // ← NEW — and already static List names, Function nameToSalesScore) { var scores = new LinkedHashMap(); for (String name : names) { scores.put(name, nameToSalesScore.apply(name)); } return scores; } ``` (`LinkedHashMap` because the demo prints names in order; a plain `HashMap` would shuffle them and confuse everyone. I say that out loud too.) **Checkpoint — the drift test:** - `grep -n scorer src/main/java/codingdojo/DailyForecasts.java` — nothing in the new method. - Try to make it `static` (it already is). If the static write had stayed in the loop, this would not compile. - `System.out` is still in `printSalesForecasts` — that is fine. One split at a time; the printing loop is next time's material. --- ## Step 5 — the overload, and the test goes green ![](svg/d5-overload-test.svg) Change Signature on the big method: the lambda becomes a parameter. A no-arg overload bakes in the real lambda, so callers are unchanged. ```java public void printSalesForecasts() { // ← no-arg overload: callers unchanged printSalesForecasts(name -> { if (Objects.equals(name, "Steve")) { Scorer.flavour = IceCream.Strawberry; } else { scorer.updateSelection(); } return scorer.getSalesForecast(); }); } public void printSalesForecasts( // ← the seam: λ is a parameter Function nameToSalesScore) { var names = List.of("Steve", "Julie", "Francis"); var scores = collectSalesScores(names, nameToSalesScore); for (var entry : scores.entrySet()) { System.out.println(entry.getKey() + " score: " + entry.getValue()); } } ``` The test goes through the seam with a pure lambda: ```java @Test void salesForecasts() { var out = new ByteArrayOutputStream(); var originalOut = System.out; System.setOut(new PrintStream(out)); try { new DailyForecasts().printSalesForecasts( name -> Objects.equals(name, "Steve") ? 9 : 11); } finally { System.setOut(originalOut); } Approvals.verify(out.toString()); } ``` Approved output: ``` Steve score: 9 Julie score: 11 Francis score: 11 ``` **Checkpoint:** green — deterministically. `Scorer` is never touched by the test. --- ## End of demo Same three moves as the exercise: extract, slide, extract. `Scorer` is out of the loop, and the lambda made it visible that the whole question is just "given a name, what is the sales score?" `System.out` and the clock are still in `printSalesForecasts` — that is the next hour, not this one.