The refactoring is now published as Slice on sammancoaching.org: https://sammancoaching.org/refactorings/slice.html - git mv ideas/split-loop-with-lambda -> ideas/slice-with-lambda - rename learning-hour file and update frontmatter name - update paths in tools/rehearse scripts and report title - update prose: 'Split Loop with Lambda' -> 'Slice with Lambda' in learning hour, exercise READMEs, storyboard, solution comments - link the published Slice refactoring from the pattern draft
51 lines
2.2 KiB
Markdown
51 lines
2.2 KiB
Markdown
# Reference solution — end state of exercise 3
|
||
|
||
Derived from [IceCreamForecasting-Refactoring-Kata](https://github.com/emilybache/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)
|
||
|
||
```csharp
|
||
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.
|