Files
learning-hour-maker/ideas/split-loop-with-lambda/exercise3/solution/README.md
T
Your Name 1189041d8e Add exercise 3 (Buyers, C#): the pure extract-lambda rep
New in-theme classes: Buyers computes a shopping-list total, PriceList
stands for the pricing service (random surge, flaky test). One loop, one
lambda-able expression — the third example teaches the move with nothing
else in the way. Pristine repo + reference solution (extract lambda,
slide, extract method with Func<IceCream,int> parameter, pure-lambda
test) + storyboard in the established style. Mirrors the kata's C#
setup (net7.0, MSTest); needs a build check on a machine with the SDK.
2026-09-28 21:48:30 +00:00

2.2 KiB
Raw Blame History

Reference solution — end state of exercise 3

Derived from IceCreamForecasting-Refactoring-Kata (same license as the kata applies to the copied IceCream.cs).

Run with dotnet test test/BuyersTests.csproj — the test passes deterministically without touching the pricing service.

What changed vs the exercise start

  1. The pricing call extracted into a lambda and slid to the top: Func<IceCream, int> priceFor = flavour => priceList.PriceFor(flavour); — the invocation in the loop stays put (priceFor(flavour)).
  2. The loop extracted to ShoppingListTotal(Buyer buyer, Func<IceCream, int> priceFor) — static-free of PriceList; all inputs are parameters. (C# note: this one is an instance method because ByName uses the buyers field; the extracted loop method itself touches no fields. If you want it static, extract the loop only and keep the lookup in the composed method.)
  3. No-arg ShoppingListTotal(string) overload keeps callers working; it passes the method group priceList.PriceFor as the "real" pricing function.
  4. The test passes a pure lambda (_ => 2) through the overload — deterministic, glanceable: 2 flavours × 2 scoops × 2 = 8.

Intermediate step (between pristine and this solution)

public int ShoppingListTotal(string buyerName)
{
    Func<IceCream, int> priceFor = flavour => priceList.PriceFor(flavour); // ← extracted & slid
    var buyer = ByName(buyerName);
    var total = 0;
    foreach (var flavour in buyer.Favourites)
    {
        total += buyer.ScoopsOf(flavour) * priceFor(flavour);              // ← invocation stays
    }
    return total;
}

Variations (debrief / second round)

  • _ => 5 — everything costs 5: Anke 20, Bram 10, Celine 20.
  • flavour => flavour == IceCream.Chocolate ? 10 : 1 — chocolate is expensive.
  • Delete PriceList entirely: the extracted method no longer knows it exists — a step-4 move is now unblocked (move ShoppingListTotal(Buyer, Func) to a class without PriceList).
  • Role-interface next step: add DiscountFor(IceCream) to PriceList (a second side-effectful method) and feel why one lambda is no longer enough — extract IPricing instead.