diff --git a/ideas/split-loop-with-lambda/exercise/csharp/solution/IceCreamShopSolution.cs b/ideas/split-loop-with-lambda/exercise/csharp/solution/IceCreamShopSolution.cs new file mode 100644 index 0000000..ecea977 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/solution/IceCreamShopSolution.cs @@ -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 _readTemperature; // the thermometer, as a lambda + private bool _thawedToday; // event-style client state + + public IceCreamShop(Func readTemperature) + { + _readTemperature = readTemperature; + } + + public ThawAmount ThawStock() + { + Func readTemperature = () => _readTemperature(); // lambda at the top + return ThawStockTail(readTemperature); // pass the lambda on + } + + private ThawAmount ThawStockTail(Func 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; + } +} \ No newline at end of file diff --git a/ideas/split-loop-with-lambda/exercise/csharp/solution/MetaphorFreeSolution.cs b/ideas/split-loop-with-lambda/exercise/csharp/solution/MetaphorFreeSolution.cs new file mode 100644 index 0000000..4eb6578 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/solution/MetaphorFreeSolution.cs @@ -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 readB) // the seam: λ is a parameter + { + if (a < 0) return 0; // guard clause: stays + return CalculateTail(a, readB); + } + + private int CalculateTail(int a, Func 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); + } +} \ No newline at end of file diff --git a/ideas/split-loop-with-lambda/exercise/csharp/solution/SliceWithLambdaTests.cs b/ideas/split-loop-with-lambda/exercise/csharp/solution/SliceWithLambdaTests.cs new file mode 100644 index 0000000..271e0ac --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/solution/SliceWithLambdaTests.cs @@ -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( + () => 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()); + } +} \ No newline at end of file diff --git a/ideas/split-loop-with-lambda/exercise/csharp/solution/WeatherStationSolution.cs b/ideas/split-loop-with-lambda/exercise/csharp/solution/WeatherStationSolution.cs new file mode 100644 index 0000000..6e50bc0 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/solution/WeatherStationSolution.cs @@ -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 _readLight; // the sensor, as a lambda + private bool _wateredToday; // event-style client state + + public SprinklerController(Func readLight) + { + _readLight = readLight; + } + + public WateringDuration ScheduleWatering() + { + Func readLight = () => _readLight(); // lambda definition at the top + return ScheduleWateringTail(readLight); // pass the lambda on + } + + private WateringDuration ScheduleWateringTail(Func 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; + } +} \ No newline at end of file