Пример #1
0
        private void ConfigureStateMachine()
        {
            // Use builder pattern to configure each state
            _stateMachine.ConfigureState(SaleState.Open)
            // Add possible transitions
            .AddTransition(SaleEvent.Cancel, SaleState.Cancelled, condition: sale => sale.IsCancellable())
            .AddTransition(SaleEvent.SetItemQuantity, SaleState.Overpaid, condition: sale => sale.HasPositiveBalance())
            .AddTransition(SaleEvent.SetItemQuantity, SaleState.Paid, condition: sale => sale.IsPaid)
            .AddTransition(SaleEvent.Pay, SaleState.Overpaid, condition: sale => sale.HasPositiveBalance())
            .AddTransition(SaleEvent.Pay, SaleState.Paid, condition: sale => sale.IsPaid)
            // Add trigger actions -- how to process requests passed from Sale
            // - Include parameter type where required
            .AddTriggerAction <SaleItem>(SaleEvent.AddItem, (sale, item) => sale.AddItemInternal(item))
            .AddTriggerAction <SaleItem>(SaleEvent.SetItemQuantity, (sale, item) => sale.SetItemQuantityInternal(item))
            .AddTriggerAction <SaleItem>(SaleEvent.DeleteItem, (sale, item) => sale.DeleteItemInternal(item))
            .AddTriggerAction <Payment>(SaleEvent.Pay, (sale, payment) => sale.AddPaymentInternal(payment))
            .AddTriggerAction <Change>(SaleEvent.GiveChange, (sale, change) => sale.AddChangeInternal(change))
            .AddTriggerAction(SaleEvent.Cancel, sale => sale.CancelInternal());

            _stateMachine.ConfigureState(SaleState.Overpaid)
            .MakeSubstateOf(_stateMachine.ConfigureState(SaleState.Open))     // This inherits all of Open's configuration unless overridden
            // Overpaid can go back to Open if the balance goes negative
            .AddTransition(SaleEvent.GiveChange, SaleState.Open, condition: sale => sale.HasNegativeBalance())
            .AddTransition(SaleEvent.AddItem, SaleState.Open, condition: sale => sale.HasNegativeBalance())
            .AddTransition(SaleEvent.SetItemQuantity, SaleState.Open, condition: sale => sale.HasNegativeBalance())
            // Can't add payments when customer over paid
            .AddTriggerAction <Payment>(SaleEvent.Pay, (sale, payment) => throw new InvalidOperationException("Cannot pay on an overpaid sale"));

            _stateMachine.ConfigureState(SaleState.Finalized)
            // This is the parent class for any finalized state -- Paid and Cancelled
            // -- No actions or transitions allowed
            .AddTriggerAction <SaleItem>(SaleEvent.AddItem, (_, item) => throw new InvalidOperationException("Cannot add item to a finalized sale"))
            .AddTriggerAction <SaleItem>(SaleEvent.SetItemQuantity, (_, item) => throw new InvalidOperationException("Cannot change item quantities a finalized sale"))
            .AddTriggerAction <SaleItem>(SaleEvent.DeleteItem, (_, item) => throw new InvalidOperationException("Cannot delete item on a finalized sale"))
            .AddTriggerAction <Payment>(SaleEvent.Pay, (_, payment) => throw new InvalidOperationException("Cannot pay on a finalized sale"))
            .AddTriggerAction <Change>(SaleEvent.GiveChange, (_, change) => throw new InvalidOperationException("Cannot give change on a finalized sale"))
            .AddTriggerAction(SaleEvent.Cancel, sale => throw new InvalidOperationException("Cannot cancel a finalized sale"));

            _stateMachine.ConfigureState(SaleState.Paid)
            .MakeSubstateOf(_stateMachine.ConfigureState(SaleState.Finalized));

            _stateMachine.ConfigureState(SaleState.Cancelled)
            .MakeSubstateOf(_stateMachine.ConfigureState(SaleState.Finalized));

#if DEBUG
            // This is how to export state configuration to Csv and/or DotGraph
            var configSummary = _stateMachine.GetSummary();
            Debug.WriteLine(CsvExporter <SaleState, SaleEvent> .Export(configSummary));
            Debug.WriteLine(DotGraphExporter <SaleState, SaleEvent> .Export(configSummary));
            // Plug the output of the DotGraphExporter into https://dreampuf.github.io/GraphvizOnline to see graph of current configuration
#endif
        }
Пример #2
0
        public void SubStates_summarized_correctly()
        {
            var stateMachine = new StateMachine <TestSale, TestSaleState, TestSaleEvent>(
                stateAccessor: sale1 => sale1.State,
                stateMutator: (sale1, state) => sale1.State = state);

            stateMachine.ConfigureState(TestSaleState.Open)
            .AddTransition(TestSaleEvent.Cancel, TestSaleState.Cancelled, condition: sale => sale.IsCancellable())
            .AddTransition(TestSaleEvent.SetItemQuantity, TestSaleState.Overpaid, condition: sale => sale.HasPositiveBalance())
            .AddTransition(TestSaleEvent.SetItemQuantity, TestSaleState.Paid, condition: sale => sale.IsPaid)
            .AddTransition(TestSaleEvent.Pay, TestSaleState.Overpaid, condition: sale => sale.HasPositiveBalance())
            .AddTransition(TestSaleEvent.Pay, TestSaleState.Paid, condition: sale => sale.IsPaid);
            stateMachine.ConfigureState(TestSaleState.Overpaid)
            .MakeSubStateOf(stateMachine.ConfigureState(TestSaleState.Open))     // This inherits all of Open's configuration unless overridden
            // Overpaid can go back to Open if the balance goes negative
            .AddTransition(TestSaleEvent.GiveChange, TestSaleState.Open, condition: sale => sale.HasNegativeBalance())
            .AddTransition(TestSaleEvent.GiveChange, TestSaleState.Paid, condition: sale => sale.IsPaid)
            .AddTransition(TestSaleEvent.AddItem, TestSaleState.Open, condition: sale => sale.HasNegativeBalance())
            .AddTransition(TestSaleEvent.SetItemQuantity, TestSaleState.Open, condition: sale => sale.HasNegativeBalance())
            .AddTransition(TestSaleEvent.SetItemQuantity, TestSaleState.Paid, condition: sale => sale.IsPaid);
            stateMachine.ConfigureState(TestSaleState.Finalized);
            // This is the parent class for any finalized state -- Paid and Cancelled
            // -- No actions or transitions allowed
            stateMachine.ConfigureState(TestSaleState.Paid)
            .MakeSubStateOf(stateMachine.ConfigureState(TestSaleState.Finalized));
            stateMachine.ConfigureState(TestSaleState.Cancelled)
            .MakeSubStateOf(stateMachine.ConfigureState(TestSaleState.Finalized));

            var configSummary = stateMachine.GetSummary();

            _testOutputHelper.WriteLine(DotGraphExporter <TestSaleState, TestSaleEvent> .Export(configSummary));

            Assert.Equal(0, configSummary.StartingStates.Count);
            Assert.Contains(configSummary.FinalStates, s => s.State == TestSaleState.Cancelled);
            Assert.Contains(configSummary.FinalStates, s => s.State == TestSaleState.Paid);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Open && t.ToState.State == TestSaleState.Cancelled && t.Trigger == TestSaleEvent.Cancel);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Open && t.ToState.State == TestSaleState.Overpaid && t.Trigger == TestSaleEvent.SetItemQuantity);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Open && t.ToState.State == TestSaleState.Paid && t.Trigger == TestSaleEvent.SetItemQuantity);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Open && t.ToState.State == TestSaleState.Overpaid && t.Trigger == TestSaleEvent.Pay);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Open && t.ToState.State == TestSaleState.Paid && t.Trigger == TestSaleEvent.Pay);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Overpaid && t.ToState.State == TestSaleState.Open && t.Trigger == TestSaleEvent.GiveChange);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Overpaid && t.ToState.State == TestSaleState.Open && t.Trigger == TestSaleEvent.AddItem);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Overpaid && t.ToState.State == TestSaleState.Open && t.Trigger == TestSaleEvent.SetItemQuantity);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Overpaid && t.ToState.State == TestSaleState.Paid && t.Trigger == TestSaleEvent.GiveChange);
            Assert.Contains(configSummary.Transitions, t => t.FromState.State == TestSaleState.Overpaid && t.ToState.State == TestSaleState.Paid && t.Trigger == TestSaleEvent.SetItemQuantity);
        }
        public void Export_exports_sale_graph()
        {
            var stateMachine = getSaleStateMachine();
            var summary      = stateMachine.GetSummary();

            var dotGraphLines = DotGraphExporter <SaleState, SaleEvent> .Export(summary)?.Split("\r\n");

            Assert.Equal("digraph NStateManagerGraph {", dotGraphLines.First());
            Assert.Equal("\trankdir=LR;", dotGraphLines[1]);
            Assert.Equal(string.Empty, dotGraphLines[2]);
            Assert.Equal(string.Empty, dotGraphLines[3]);
            Assert.Equal("\tOpen;", dotGraphLines[4]);
            Assert.Equal("\tChangeDue;", dotGraphLines[5]);
            Assert.Equal("\tComplete;", dotGraphLines[6]);
            Assert.Equal(string.Empty, dotGraphLines[7]);
            Assert.Equal("\tOpen -> Complete [label=\"Pay(*)\"];", dotGraphLines[8]);
            Assert.Equal("\tOpen -> ChangeDue [label=\"Pay(*)\"];", dotGraphLines[9]);
        }
        public void Export_exports_graph_with_substates()
        {
            var stateMachine = getComplexSaleStateMachine();
            var summary      = stateMachine.GetSummary();

            var dotGraphLines = DotGraphExporter <SaleComplexState, SaleComplexEvent> .Export(summary)?.Split("\r\n");

            Assert.Equal("digraph NStateManagerGraph {", dotGraphLines[0]);
            Assert.Equal("\tcompound=true;", dotGraphLines[1]);
            Assert.Equal("\trankdir=LR;", dotGraphLines[2]);
            Assert.Equal(string.Empty, dotGraphLines[3]);
            Assert.Equal(string.Empty, dotGraphLines[4]);
            Assert.Equal("\tsubgraph cluster_Open {", dotGraphLines[5]);
            Assert.Equal("\t\tlabel=Open;", dotGraphLines[6]);
            Assert.Equal("\t\tOpen [shape=circle label=\"\" style=filled color=black]", dotGraphLines[7]);
            Assert.Equal(string.Empty, dotGraphLines[8]);
            Assert.Equal("\t\tOverpaid;", dotGraphLines[9]);
            Assert.Equal("\t}", dotGraphLines[10]);
            Assert.Equal(string.Empty, dotGraphLines[11]);
            Assert.Equal("\tsubgraph cluster_Finalized {", dotGraphLines[12]);
            Assert.Equal("\t\tlabel=Finalized;", dotGraphLines[13]);
            Assert.Equal("\t\tFinalized [shape=point color=white];", dotGraphLines[14]);
            Assert.Equal(string.Empty, dotGraphLines[15]);
            Assert.Equal("\t\tPaid;", dotGraphLines[16]);
            Assert.Equal("\t\tCancelled;", dotGraphLines[17]);
            Assert.Equal("\t}", dotGraphLines[18]);
            Assert.Equal(string.Empty, dotGraphLines[19]);
            Assert.Equal("\tOpen -> Cancelled [label=\"Cancel(*)\" ltail=cluster_Open];", dotGraphLines[20]);
            Assert.Equal("\tOpen -> Overpaid [label=\"SetItemQuantity(*)\" ltail=cluster_Open];", dotGraphLines[21]);
            Assert.Equal("\tOpen -> Paid [label=\"SetItemQuantity(*)\" ltail=cluster_Open];", dotGraphLines[22]);
            Assert.Equal("\tOpen -> Overpaid [label=\"Pay(*)\" ltail=cluster_Open];", dotGraphLines[23]);
            Assert.Equal("\tOpen -> Paid [label=\"Pay(*)\" ltail=cluster_Open];", dotGraphLines[24]);
            Assert.Equal("\tOverpaid -> Open [label=\"GiveChange(*)\" lhead=cluster_Open];", dotGraphLines[25]);
            Assert.Equal("\tOverpaid -> Open [label=\"AddItem(*)\" lhead=cluster_Open];", dotGraphLines[26]);
            Assert.Equal("\tOverpaid -> Open [label=\"SetItemQuantity(*)\" lhead=cluster_Open];", dotGraphLines[27]);
            Assert.Equal("}", dotGraphLines[28]);
        }
Пример #5
0
 public void Export_exports_sale_graph()
 {
     var stateMachine = GetSaleStateMachine();
     var summary      = stateMachine.GetSummary();
     var dotGraph     = DotGraphExporter <SaleState, SaleEvent> .Export(summary);
 }
Пример #6
0
 public void Export_exports_phone_graph()
 {
     var stateMachine = new PhoneCall("Scott");
     var summary      = stateMachine.GetSummary();
     var dotGraph     = DotGraphExporter <State, Trigger> .Export(summary);
 }
Пример #7
0
 public static string GetDotGraph()
 {
     return(DotGraphExporter <PhoneState, PhoneEvent> .Export(_stateMachine.GetSummary()));
 }