Add solution (after) files for the three C# demos: seam overloads, extracted tails, pure xUnit stub tests

This commit is contained in:
Your Name
2026-09-29 13:32:47 +00:00
parent f9c3c68e79
commit 578162851b
4 changed files with 278 additions and 0 deletions
@@ -0,0 +1,72 @@
// Example 3 solution — Ice cream shop demo of Slice with Lambda (C#), after
//
// Lambda extracted (step 1), slid to the top (step 2), method extracted
// below the guard (step 3). The constructor now takes the lambda directly,
// so tests construct with new IceCreamShop(() => 30).
//
// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html
// (where that page says mock, we use a lambda instead).
using System;
public enum Warmth
{
Cold,
Warm
}
public enum ThawAmount
{
None,
Medium,
Low
}
public class Thermometer
{
private readonly Random _rng = new Random(); // stand-in for the hardware
public byte Read()
{
return (byte)_rng.Next(50); // raw sensor value
}
}
public class IceCreamShop
{
private readonly Func<byte> _readTemperature; // the thermometer, as a lambda
private bool _thawedToday; // event-style client state
public IceCreamShop(Func<byte> readTemperature)
{
_readTemperature = readTemperature;
}
public ThawAmount ThawStock()
{
Func<byte> readTemperature = () => _readTemperature(); // lambda at the top
return ThawStockTail(readTemperature); // pass the lambda on
}
private ThawAmount ThawStockTail(Func<byte> readTemperature)
{
if (_thawedToday) // guard: client state, stays
{
return ThawAmount.None;
}
_thawedToday = true; // set only after thawing
byte temperature = readTemperature(); // middle: the stubbed lambda
Warmth warmth = ToWarmth(temperature);
if (warmth == Warmth.Warm) // tail: complex behaviour below
{ // the extraction point
return ThawAmount.Medium;
}
return ThawAmount.Low;
}
private static Warmth ToWarmth(byte temperature)
{
return temperature > 25 ? Warmth.Warm : Warmth.Cold;
}
}
@@ -0,0 +1,52 @@
// Example 1 solution — Metaphor-free demo of Slice with Lambda (C#), after
//
// Lambda extracted (step 1), slid to the top (step 2), method extracted
// below the guard (step 3), overload seam added (step 3b), b inlined
// (final cleanup).
//
// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html
// (where that page says mock, we use a lambda instead).
using System;
public class Calculator
{
private readonly SomeDependency _someDependency;
public Calculator(SomeDependency someDependency)
{
_someDependency = someDependency;
}
public int Calculate(int a) // overload: callers unchanged
{
return Calculate(a, a =>
_someDependency.mightThrowWhenPreconditionNotSatisfied(a));
} // the real λ, baked into the head
public int Calculate(int a, Func<int, int> readB) // the seam: λ is a parameter
{
if (a < 0) return 0; // guard clause: stays
return CalculateTail(a, readB);
}
private int CalculateTail(int a, Func<int, int> readB)
{
int c = a + readB(a); // b inlined
return c * 2;
}
}
public class SomeDependency
{
private readonly Random _rng = new Random(); // stand-in for unpredictable computation
public int mightThrowWhenPreconditionNotSatisfied(int a)
{
if (a > 99)
{
throw new InvalidOperationException("precondition not satisfied");
}
return _rng.Next(100);
}
}
@@ -0,0 +1,82 @@
// Tests for the three Slice-with-Lambda solutions — step 4: the lambda as a stub.
//
// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html
// (where that page says mock, we use a lambda instead).
using System;
using Xunit;
public class SliceWithLambdaTests
{
// --- Example 1: metaphor-free (Calculator) ---
[Fact]
public void Guards_against_negative_input()
{
var calc = new Calculator(new SomeDependency()); // the real, flaky one...
Assert.Equal(0, calc.Calculate(-1, a => 42)); // ...never asked: stub not invoked
}
[Fact]
public void Throws_when_precondition_not_satisfied()
{
var calc = new Calculator(new SomeDependency());
Assert.Throws<InvalidOperationException>(
() => calc.Calculate(7, a => throw new InvalidOperationException()));
}
[Fact]
public void Combines_input_and_the_stubbed_value()
{
var calc = new Calculator(new SomeDependency());
Assert.Equal(84, calc.Calculate(0, a => 42)); // stub through the seam
}
// --- Example 2: Weather Station (SprinklerController) ---
[Fact]
public void Skips_watering_when_already_watered()
{
var controller = new SprinklerController(() => 200); // pure lambda: no sensor
controller.ScheduleWatering(); // water once...
Assert.Equal(WateringDuration.None, controller.ScheduleWatering());
}
[Fact]
public void Waters_longer_in_full_sun()
{
var controller = new SprinklerController(() => 200); // sunny: > 128
Assert.Equal(WateringDuration.Medium, controller.ScheduleWatering());
}
[Fact]
public void Waters_less_in_the_shade()
{
var controller = new SprinklerController(() => 10); // shady: ≤ 128
Assert.Equal(WateringDuration.Short, controller.ScheduleWatering());
}
// --- Example 3: Ice Cream Shop (IceCreamShop) ---
[Fact]
public void Skips_thawing_when_already_thawed()
{
var shop = new IceCreamShop(() => 30); // pure lambda: no freezer
shop.ThawStock(); // thaw once...
Assert.Equal(ThawAmount.None, shop.ThawStock());
}
[Fact]
public void Thaws_more_when_warm()
{
var shop = new IceCreamShop(() => 30); // warm: > 25
Assert.Equal(ThawAmount.Medium, shop.ThawStock());
}
[Fact]
public void Thaws_less_when_cold()
{
var shop = new IceCreamShop(() => 5); // cold: ≤ 25
Assert.Equal(ThawAmount.Low, shop.ThawStock());
}
}
@@ -0,0 +1,72 @@
// Example 2 solution — Weather Station domain demo of Slice with Lambda (C#), after
//
// Lambda extracted (step 1), slid to the top (step 2), method extracted
// below the guard (step 3). The constructor now takes the lambda directly,
// so tests construct with new SprinklerController(() => 200).
//
// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html
// (where that page says mock, we use a lambda instead).
using System;
public enum SunExposure
{
Shady,
Sunny
}
public enum WateringDuration
{
None,
Medium,
Short
}
public class LightSensor
{
private readonly Random _rng = new Random(); // stand-in for hardware noise
public byte Read()
{
return (byte)_rng.Next(256);
}
}
public class SprinklerController
{
private readonly Func<byte> _readLight; // the sensor, as a lambda
private bool _wateredToday; // event-style client state
public SprinklerController(Func<byte> readLight)
{
_readLight = readLight;
}
public WateringDuration ScheduleWatering()
{
Func<byte> readLight = () => _readLight(); // lambda definition at the top
return ScheduleWateringTail(readLight); // pass the lambda on
}
private WateringDuration ScheduleWateringTail(Func<byte> readLight)
{
if (_wateredToday) // guard: client state, stays
{
return WateringDuration.None;
}
_wateredToday = true; // set only after watering
byte reading = readLight(); // middle: the stubbed lambda
SunExposure exposure = ToSunExposure(reading);
if (exposure == SunExposure.Sunny) // tail: complex behaviour below
{ // the extraction point
return WateringDuration.Medium;
}
return WateringDuration.Short;
}
private static SunExposure ToSunExposure(byte reading)
{
return reading > 128 ? SunExposure.Sunny : SunExposure.Shady;
}
}