From db5760b3362e3a165135caa422175391267c8634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=BD=E7=AC=94?= <46271592+Skymly@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:22:28 +0800 Subject: [PATCH] Add State v2 guard and DI integration demos to State sample Extends the State sample to demonstrate v2 features: - Guard delegates on [Transition] with CanCancelDraft/CanCancelSubmitted static guard methods on the holder class - DI integration via generated RegisterDi + AddTransitionTable extension - GetAllowedTriggers output showing terminal state (Paid) - Cancelled state added to enum for richer edge coverage - DesignPatternsSampleKind changed from Core to DependencyInjection to pull in the DI extension and enable DesignPatterns_EnableDiIntegration --- .../DesignPatterns.Samples.State.csproj | 4 +- DesignPatterns.Samples.State/OrderMachine.cs | 21 ++++++- DesignPatterns.Samples.State/Program.cs | 55 ++++++++++++++++--- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj b/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj index ea758c0..08be9f5 100644 --- a/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj +++ b/DesignPatterns.Samples.State/DesignPatterns.Samples.State.csproj @@ -6,8 +6,8 @@ enable enable DesignPatterns.Samples.State - Core - Console sample for state transition tables (manual builder and [StateMachine] generator). + DependencyInjection + Console sample for state transition tables: manual builder, [StateMachine] generator, guard delegates, and DI integration. diff --git a/DesignPatterns.Samples.State/OrderMachine.cs b/DesignPatterns.Samples.State/OrderMachine.cs index 7cd6529..d288052 100644 --- a/DesignPatterns.Samples.State/OrderMachine.cs +++ b/DesignPatterns.Samples.State/OrderMachine.cs @@ -1,21 +1,38 @@ using DesignPatterns.Behavioral; -namespace State.Sample; +namespace DesignPatterns.Samples.State; public enum OrderStatus { Draft, Submitted, Paid, + Cancelled, } public enum OrderTrigger { Submit, Pay, + Cancel, } [StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] [Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] +[Transition(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelDraft))] [Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] -public static partial class OrderMachine; +[Transition(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled, Guard = nameof(CanCancelSubmitted))] +public static partial class OrderMachine +{ + /// + /// Guard: draft orders can only be cancelled when no items have been submitted yet. + /// Demonstrates a guard that always passes in the sample (no domain state). + /// + public static bool CanCancelDraft(OrderStatus state, OrderTrigger trigger) => true; + + /// + /// Guard: submitted orders can only be cancelled before payment is processed. + /// Demonstrates a guard that always passes in the sample (no domain state). + /// + public static bool CanCancelSubmitted(OrderStatus state, OrderTrigger trigger) => true; +} diff --git a/DesignPatterns.Samples.State/Program.cs b/DesignPatterns.Samples.State/Program.cs index 084e951..853dad3 100644 --- a/DesignPatterns.Samples.State/Program.cs +++ b/DesignPatterns.Samples.State/Program.cs @@ -1,26 +1,67 @@ using DesignPatterns.Behavioral; -using State.Sample; +using DesignPatterns.Extensions.DependencyInjection; +using DesignPatterns.Samples.State; +using Microsoft.Extensions.DependencyInjection; -Console.WriteLine("=== Manual TransitionTableBuilder ==="); +Console.WriteLine("=== Manual TransitionTableBuilder (with guard) ==="); var manualTable = new TransitionTableBuilder() .WithInitial(OrderStatus.Draft) .Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted) + .Add(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled, + guard: (state, trigger) => true) // guard always passes in sample .Add(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid) + .Add(OrderStatus.Submitted, OrderTrigger.Cancel, OrderStatus.Cancelled, + guard: (state, trigger) => true) .Build(); RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Submit); RunTransition(manualTable, OrderStatus.Submitted, OrderTrigger.Pay); +RunTransition(manualTable, OrderStatus.Draft, OrderTrigger.Cancel); AssertInvalid(manualTable, OrderStatus.Paid, OrderTrigger.Pay); Console.WriteLine(); -Console.WriteLine("=== Generated [StateMachine] table ==="); +Console.WriteLine("=== Generated [StateMachine] table (with guard) ==="); Console.WriteLine($"Initial state: {OrderMachine.InitialState}"); RunGenerated(OrderStatus.Draft, OrderTrigger.Submit); RunGenerated(OrderStatus.Submitted, OrderTrigger.Pay); +RunGenerated(OrderStatus.Draft, OrderTrigger.Cancel); +RunGenerated(OrderStatus.Submitted, OrderTrigger.Cancel); AssertInvalidGenerated(OrderStatus.Paid, OrderTrigger.Pay); +Console.WriteLine(); +Console.WriteLine("=== DI integration (RegisterDi + AddTransitionTable) ==="); + +// Generated RegisterDi: registers ITransitionTable as singleton +var services = new ServiceCollection(); +OrderStatusTransitionTable.RegisterDi(services); + +// Also demonstrate manual AddTransitionTable extension (TryAdd idempotent — won't overwrite) +services.AddTransitionTable(OrderStatusTransitionTable.Instance); + +var provider = services.BuildServiceProvider(); +var diTable = provider.GetRequiredService>(); + +Console.WriteLine($"Resolved from DI: {diTable.GetType().Name}"); +Console.WriteLine($"DI table initial state: {diTable.InitialState}"); + +RunTransition(diTable, OrderStatus.Draft, OrderTrigger.Submit); +RunTransition(diTable, OrderStatus.Submitted, OrderTrigger.Pay); +AssertInvalid(diTable, OrderStatus.Cancelled, OrderTrigger.Submit); + +Console.WriteLine(); +Console.WriteLine("=== GetAllowedTriggers ==="); + +var allowed = diTable.GetAllowedTriggers(OrderStatus.Draft); +Console.WriteLine($"Allowed triggers from Draft: {string.Join(", ", allowed)}"); + +var submittedAllowed = diTable.GetAllowedTriggers(OrderStatus.Submitted); +Console.WriteLine($"Allowed triggers from Submitted: {string.Join(", ", submittedAllowed)}"); + +var paidAllowed = diTable.GetAllowedTriggers(OrderStatus.Paid); +Console.WriteLine($"Allowed triggers from Paid (terminal): {(paidAllowed.Count == 0 ? "(none — terminal state)" : string.Join(", ", paidAllowed))}"); + static void RunTransition( ITransitionTable table, OrderStatus current, @@ -28,7 +69,7 @@ static void RunTransition( { if (table.TryTransition(current, trigger, out var next)) { - Console.WriteLine($"{current} + {trigger} -> {next}"); + Console.WriteLine($" {current} + {trigger} -> {next}"); } } @@ -39,7 +80,7 @@ static void AssertInvalid( { if (!table.TryTransition(current, trigger, out _)) { - Console.WriteLine($"{current} + {trigger} is invalid (expected)."); + Console.WriteLine($" {current} + {trigger} is invalid (expected)."); } } @@ -47,7 +88,7 @@ static void RunGenerated(OrderStatus current, OrderTrigger trigger) { if (OrderMachine.TryTransition(current, trigger, out var next)) { - Console.WriteLine($"{current} + {trigger} -> {next}"); + Console.WriteLine($" {current} + {trigger} -> {next}"); } } @@ -55,6 +96,6 @@ static void AssertInvalidGenerated(OrderStatus current, OrderTrigger trigger) { if (!OrderMachine.TryTransition(current, trigger, out _)) { - Console.WriteLine($"{current} + {trigger} is invalid (expected)."); + Console.WriteLine($" {current} + {trigger} is invalid (expected)."); } }