เช่นเดียวกับกระรอก สัตว์ ขนาดเล็ก ว่องไว ฉลาด ตื่นตัว และ น่ารัก มูลนิธิ กระรอกมีเป้าหมายเพื่อให้มี น้ำหนักเบา มีความยืดหยุ่น สูงและ ขยายได้ วินิจฉัยได้ ใช้งานง่าย และ พิมพ์การใช้งานเครื่องสถานะ Java ที่ปลอดภัย สำหรับการใช้งานระดับองค์กร
นี่คือแผนภาพเครื่องสถานะซึ่งอธิบายการเปลี่ยนแปลงสถานะของ ATM:
โค้ดตัวอย่างสามารถพบได้ในแพ็คเกจ "org.squirrelframework.foundation.fsm.atm"
มูลนิธิกระรอกได้รับการปรับใช้กับพื้นที่เก็บข้อมูลส่วนกลางของ maven ดังนั้นคุณเพียงแค่ต้องเพิ่มการพึ่งพาต่อไปนี้ใน pom.xml
เวอร์ชันล่าสุดที่ออก:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.10</version>
</dependency>
เวอร์ชันภาพรวมล่าสุด:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.11-SNAPSHOT</version>
</dependency>
หากต้องการลองใช้ฟังก์ชันเครื่องสถานะกระรอกอย่างรวดเร็ว โปรดสร้างโปรเจ็กต์ Maven และรวมการขึ้นต่อกันของกระรอกพื้นฐานอย่างเหมาะสม จากนั้นเพียงเรียกใช้โค้ดตัวอย่างต่อไปนี้
public class QuickStartSample {
// 1. Define State Machine Event
enum FSMEvent {
ToA , ToB , ToC , ToD
}
// 2. Define State Machine Class
@ StateMachineParameters ( stateType = String . class , eventType = FSMEvent . class , contextType = Integer . class )
static class StateMachineSample extends AbstractUntypedStateMachine {
protected void fromAToB ( String from , String to , FSMEvent event , Integer context ) {
System . out . println ( "Transition from '" + from + "' to '" + to + "' on event '" + event +
"' with context '" + context + "'." );
}
protected void ontoB ( String from , String to , FSMEvent event , Integer context ) {
System . out . println ( "Entry State ' " + to + " ' ." );
}
}
public static void main ( String [] args ) {
// 3. Build State Transitions
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create ( StateMachineSample . class );
builder . externalTransition (). from ( "A" ). to ( "B" ). on ( FSMEvent . ToB ). callMethod ( "fromAToB" );
builder . onEntry ( "B" ). callMethod ( "ontoB" );
// 4. Use State Machine
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
fsm . fire ( FSMEvent . ToB , 10 );
System . out . println ( "Current state is " + fsm . getCurrentState ());
}
}
ขณะนี้คุณอาจมีคำถามมากมายเกี่ยวกับโค้ดตัวอย่าง โปรดอดทนรอ คู่มือผู้ใช้ต่อไปนี้จะตอบคำถามส่วนใหญ่ของคุณ แต่ก่อนที่จะลงรายละเอียด คุณต้องมีความเข้าใจพื้นฐานเกี่ยวกับแนวคิดของเครื่องสถานะ วัสดุเหล่านี้ดีสำหรับการทำความเข้าใจแนวคิดของเครื่องจักรสถานะ [state-machine-diagrams] [qt-state-machine]
มูลนิธิกระรอก รองรับทั้ง API ได้อย่างคล่องแคล่วและลักษณะการประกาศเพื่อประกาศเครื่องสถานะ และยังช่วยให้ผู้ใช้สามารถกำหนดวิธีดำเนินการในลักษณะตรงไปตรงมา
อินเทอร์เฟซ StateMachine รับพารามิเตอร์ประเภททั่วไปสี่ตัว
ผู้สร้างเครื่องจักรของรัฐ
ในการสร้างเครื่องสถานะ ผู้ใช้จำเป็นต้องสร้างเครื่องสร้างเครื่องสถานะก่อน ตัวอย่างเช่น:
StateMachineBuilder < MyStateMachine , MyState , MyEvent , MyContext > builder =
StateMachineBuilderFactory . create ( MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
ตัวสร้างเครื่องจักรสถานะใช้สำหรับพารามิเตอร์ซึ่งเป็นประเภทของเครื่องสถานะ (T) สถานะ (S) เหตุการณ์ (E) และบริบท (C)
API ได้อย่างคล่องแคล่ว
หลังจากที่สร้างเครื่องสถานะแล้ว เราสามารถใช้ API ได้อย่างคล่องแคล่วเพื่อกำหนดสถานะ/การเปลี่ยนแปลง/การทำงานของเครื่องสถานะ
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . GoToB );
การเปลี่ยนแปลงภายนอก ถูกสร้างขึ้นระหว่างสถานะ 'A' ถึงสถานะ 'B' และทริกเกอร์เมื่อได้รับเหตุการณ์ 'GoToB'
builder . internalTransition ( TransitionPriority . HIGH ). within ( MyState . A ). on ( MyEvent . WithinA ). perform ( myAction );
การเปลี่ยนแปลงภายใน ที่มีการตั้งค่าลำดับความสำคัญเป็นสูงจะสร้างภายในสถานะ 'A' ในเหตุการณ์ 'WithinA' ดำเนินการ 'myAction' การเปลี่ยนแปลงภายในหมายถึงหลังจากการเปลี่ยนแปลงเสร็จสมบูรณ์ จะไม่มีสถานะใดออกหรือป้อน ลำดับความสำคัญของการเปลี่ยนแปลงใช้เพื่อแทนที่การเปลี่ยนแปลงดั้งเดิมเมื่อเครื่องสถานะขยาย
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). when (
new Condition < MyContext >() {
@ Override
public boolean isSatisfied ( MyContext context ) {
return context != null && context . getValue ()> 80 ;
}
@ Override
public String name () {
return "MyCondition" ;
}
}). callMethod ( "myInternalTransitionCall" );
การเปลี่ยนแปลงแบบมีเงื่อนไข ถูกสร้างขึ้นจากสถานะ 'C' เป็นสถานะ 'D' ในเหตุการณ์ 'GoToD' เมื่อบริบทภายนอกเป็นไปตามข้อจำกัดของเงื่อนไข จากนั้นจึงเรียกวิธีดำเนินการ "myInternalTransitionCall" ผู้ใช้ยังสามารถใช้ MVEL (ภาษานิพจน์ที่ทรงพลัง) เพื่ออธิบายเงื่อนไขได้ดังต่อไปนี้
builder . externalTransition (). from ( MyState . C ). to ( MyState . D ). on ( MyEvent . GoToD ). whenMvel (
"MyCondition:::(context!=null && context.getValue()>80)" ). callMethod ( "myInternalTransitionCall" );
หมายเหตุ: อักขระ ':::' ใช้เพื่อแยกชื่อเงื่อนไขและนิพจน์เงื่อนไข 'บริบท' เป็นจุดตัวแปรที่กำหนดไว้ล่วงหน้าไปยังวัตถุบริบทปัจจุบัน
builder . onEntry ( MyState . A ). perform ( Lists . newArrayList ( action1 , action2 ))
รายการการดำเนินการรายการสถานะถูกกำหนดไว้ในโค้ดตัวอย่างด้านบน
วิธีการเรียกการดำเนินการ
ผู้ใช้สามารถกำหนดการกระทำที่ไม่ระบุชื่อในระหว่างการกำหนดการเปลี่ยนหรือเข้า/ออกสถานะ อย่างไรก็ตาม โค้ดการดำเนินการจะกระจัดกระจายอยู่ในหลายๆ ที่ซึ่งอาจทำให้โค้ดยากต่อการดูแลรักษา นอกจากนี้ ผู้ใช้รายอื่นไม่สามารถแทนที่การกระทำได้ ดังนั้นมูลนิธิกระรอกยังสนับสนุนการกำหนดการดำเนินการเรียกเมธอดเครื่องของรัฐซึ่งมาพร้อมกับคลาสเครื่องของรัฐเอง
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (
MyStateMachine . class , MyState . class , MyEvent . class , MyContext . class );
builder . externalTransition (). from ( A ). to ( B ). on ( toB ). callMethod ( "fromAToB" );
// All transition action method stays with state machine class
public class MyStateMachine <...> extends AbstractStateMachine <...> {
protected void fromAToB ( MyState from , MyState to , MyEvent event , MyContext context ) {
// this method will be called during transition from "A" to "B" on event "toB"
// the action method parameters types and order should match
...
}
}
นอกจากนี้ มูลนิธิกระรอกยังสนับสนุนการกำหนดการดำเนินการเรียกเมธอดในลักษณะ Convention Over Configuration โดยพื้นฐานแล้ว หมายความว่าหากวิธีการประกาศในเครื่องสถานะเป็นไปตามหลักการตั้งชื่อและพารามิเตอร์ วิธีการนั้นจะถูกเพิ่มเข้าไปในรายการการดำเนินการการเปลี่ยนแปลงและจะถูกเรียกใช้ในบางเฟสด้วย เช่น
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
วิธีการชื่อเป็น TransitFrom[SourceStateName]To[TargetStateName]On[EventName] และกำหนดพารามิเตอร์เป็น [MyState, MyState, MyEvent, MyContext] จะถูกเพิ่มเข้าไปในรายการการดำเนินการการเปลี่ยนแปลง "A-(GoToB)->B" เมื่อเปลี่ยนจากสถานะ 'A' ไปเป็นสถานะ 'B' ในเหตุการณ์ 'GoToB' วิธีการนี้จะถูกเรียกใช้
protected void transitFromAnyToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
TransitFromAnyTo[TargetStateName]On[EventName] วิธีการจะถูกเรียกใช้เมื่อมีการเปลี่ยนผ่านจากสถานะใด ๆ ไปยังสถานะ 'B' ในเหตุการณ์ 'GoToB'
protected void exitA ( MyState from , MyState to , MyEvent event , MyContext context )
exit[StateName] วิธีการจะถูกเรียกใช้เมื่อสถานะทางออก 'A' ดังนั้นเป็น entry[StateName] , beforeExitAny / afterExitAny และ beforeEntryAny / afterEntryAny
รูปแบบการตั้งชื่ออื่นๆ ที่รองรับ:
transitFrom[fromStateName]To[toStateName]On[eventName]When[conditionName]
transitFrom[fromStateName]To[toStateName]On[eventName]
transitFromAnyTo[toStateName]On[eventName]
transitFrom[fromStateName]ToAnyOn[eventName]
transitFrom[fromStateName]To[toStateName]
on[eventName]
แบบแผนวิธีการเหล่านั้นที่กล่าวข้างต้นยังมีฟังก์ชัน ที่คล้ายกับ AOP ซึ่งให้ความสามารถในการขยายที่ยืดหยุ่นในตัวสำหรับเครื่องสถานะกระรอกในทุกรายละเอียด สำหรับข้อมูลเพิ่มเติม โปรดดูกรณีทดสอบ " org.squirrelframework.foundation.fsm.ExtensionMethodCallTest " ตั้งแต่ 0.3.1 มีอีกวิธีหนึ่งในการกำหนดวิธีการขยายที่คล้ายกับ AOP เหล่านี้ซึ่งผ่าน API ได้อย่างคล่องแคล่ว (ขอบคุณคำแนะนำจาก vittali) เช่น
// since 0.3.1
// the same effect as add method transitFromAnyToCOnToC in your state machine
builder . transit (). fromAny (). to ( "C" ). on ( "ToC" ). callMethod ( "fromAnyToC" );
// the same effect as add method transitFromBToAnyOnToC in your state machine
builder . transit (). from ( "B" ). toAny (). on ( "ToC" ). callMethod ( "fromBToAny" );
// the same effect as add method transitFromBToAny in your state machine
builder . transit (). from ( "B" ). toAny (). onAny (). callMethod ( "fromBToAny" );
หรือผ่านคำอธิบายประกอบที่ประกาศเช่น
// since 0.3.1
@ Transitions ({
@ Transit ( from = "B" , to = "E" , on = "*" , callMethod = "fromBToEOnAny" ),
@ Transit ( from = "*" , to = "E" , on = "ToE" , callMethod = "fromAnyToEOnToE" )
})
หมายเหตุ : วิธีดำเนินการเหล่านี้จะแนบไปกับ การเปลี่ยนที่ตรงกันและมีอยู่แล้ว แต่จะไม่สร้างการเปลี่ยนใหม่ ตั้งแต่ 0.3.4 เป็นต้นไป การเปลี่ยนหลายรายการสามารถกำหนดได้ครั้งละครั้งโดยใช้ API ต่อไปนี้ เช่น
// transitions(A->B@A2B=>a2b, A->C@A2C=>a2c, A->D@A2D) will be defined at once
builder . transitions (). from ( State . _A ). toAmong ( State . B , State . C , State . D ).
onEach ( Event . A2B , Event . A2C , Event . A2D ). callMethod ( "a2b|a2c|_" );
// transitions(A->_A@A2ANY=>DecisionMaker, _A->A@ANY2A) will be defined at once
builder . localTransitions (). between ( State . A ). and ( State . _A ).
onMutual ( Event . A2ANY , Event . ANY2A ).
perform ( Lists . newArrayList ( new DecisionMaker ( "SomeLocalState" ), null ) );
สามารถดูข้อมูลเพิ่มเติมได้ใน org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest
คำอธิบายประกอบที่ประกาศ
นอกจากนี้ยังมีวิธีการประกาศเพื่อกำหนดและขยายเครื่องสถานะด้วย นี่คือตัวอย่าง
@ States ({
@ State ( name = "A" , entryCallMethod = "entryStateA" , exitCallMethod = "exitStateA" ),
@ State ( name = "B" , entryCallMethod = "entryStateB" , exitCallMethod = "exitStateB" )
})
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "GoToB" , callMethod = "stateAToStateBOnGotoB" ),
@ Transit ( from = "A" , to = "A" , on = "WithinA" , callMethod = "stateAToStateAOnWithinA" , type = TransitionType . INTERNAL )
})
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
void entryStateA ( MyState from , MyState to , MyEvent event , MyContext context );
void stateAToStateBOnGotoB ( MyState from , MyState to , MyEvent event , MyContext context )
void stateAToStateAOnWithinA ( MyState from , MyState to , MyEvent event , MyContext context )
void exitStateA ( MyState from , MyState to , MyEvent event , MyContext context );
...
}
คำอธิบายประกอบสามารถกำหนดได้ทั้งในคลาสการใช้งานของเครื่องสถานะหรืออินเทอร์เฟซใดๆ ที่เครื่องสถานะจะถูกนำไปใช้ นอกจากนี้ยังสามารถใช้ร่วมกับ Fluent API ได้ ซึ่งหมายความว่าเครื่องสถานะที่กำหนดใน Fluent API ยังสามารถขยายได้ด้วยคำอธิบายประกอบเหล่านี้ (สิ่งหนึ่งที่คุณอาจต้องสังเกตคือ วิธีการที่กำหนดไว้ภายในอินเทอร์เฟซจะต้องเป็นแบบสาธารณะ ซึ่งหมายความว่าการดำเนินการเรียกเมธอดนั้นจะเป็นแบบสาธารณะสำหรับผู้โทร)
ตัวแปลง
ในการประกาศสถานะและเหตุการณ์ภายใน @State และ @Transit ผู้ใช้จำเป็นต้องใช้ตัวแปลงที่สอดคล้องกันสำหรับประเภท state(S) และ event(E) การแปลงต้องใช้อินเทอร์เฟซ Converter<T> ซึ่งจะแปลงสถานะ/เหตุการณ์เป็น/จากสตริง
public interface Converter < T > extends SquirrelComponent {
/**
* Convert object to string.
* @param obj converted object
* @return string description of object
*/
String convertToString ( T obj );
/**
* Convert string to object.
* @param name name of the object
* @return converted object
*/
T convertFromString ( String name );
}
จากนั้นลงทะเบียนตัวแปลงเหล่านี้เป็น ConverterProvider เช่น
ConverterProvider . INSTANCE . register ( MyEvent . class , new MyEventConverter ());
ConverterProvider . INSTANCE . register ( MyState . class , new MyStateConverter ());
หมายเหตุ: หากคุณใช้เฉพาะ API ได้อย่างคล่องแคล่วเพื่อกำหนดเครื่องสถานะ ก็ไม่จำเป็นต้องใช้ตัวแปลงที่เกี่ยวข้อง และหากคลาส Event หรือ State เป็นประเภท String หรือการแจงนับ คุณไม่จำเป็นต้องนำไปใช้หรือลงทะเบียนตัวแปลงอย่างชัดเจนในกรณีส่วนใหญ่
อินสแตนซ์เครื่องสถานะใหม่
หลังจากที่ผู้ใช้กำหนดพฤติกรรมเครื่องสถานะแล้ว ผู้ใช้สามารถสร้างอินสแตนซ์เครื่องสถานะใหม่ผ่านตัวสร้างได้ โปรดทราบว่า เมื่อสร้างอินสแตนซ์เครื่องสถานะจากตัวสร้างแล้ว ตัวสร้างจะไม่สามารถใช้เพื่อกำหนดองค์ประกอบใหม่ของเครื่องสถานะได้อีกต่อไป
T newStateMachine ( S initialStateId , Object ... extraParams );
หากต้องการสร้างอินสแตนซ์เครื่องสถานะใหม่จากตัวสร้างเครื่องสถานะ คุณต้องผ่านพารามิเตอร์ต่อไปนี้
initialStateId
: เมื่อเริ่มต้น สถานะเริ่มต้นของเครื่องสถานะ
extraParams
: พารามิเตอร์พิเศษที่จำเป็นสำหรับการสร้างอินสแตนซ์เครื่องสถานะใหม่ ตั้งค่าเป็น "วัตถุใหม่[0]" โดยไม่จำเป็นต้องมีพารามิเตอร์เพิ่มเติม
ก. หากผู้ใช้ส่งพารามิเตอร์เพิ่มเติมในขณะที่สร้างอินสแตนซ์เครื่องสถานะใหม่ โปรดตรวจสอบให้แน่ใจว่า StateMachineBuilderFactory ได้กำหนดประเภทของพารามิเตอร์พิเศษไว้ด้วยเมื่อสร้างตัวสร้างเครื่องสถานะ มิฉะนั้น พารามิเตอร์พิเศษจะถูกละเว้น ข. พารามิเตอร์พิเศษสามารถส่งผ่านไปยังอินสแตนซ์ของเครื่องสถานะได้สองวิธี วิธีแรกคือผ่านตัวสร้างเครื่องสถานะ ซึ่งหมายความว่าผู้ใช้จำเป็นต้องกำหนดตัวสร้างด้วยประเภทพารามิเตอร์เดียวกันและลำดับสำหรับอินสแตนซ์เครื่องสถานะ อีกวิธีหนึ่งคือกำหนดวิธีการชื่อ postConstruct
และมีประเภทและลำดับของพารามิเตอร์เดียวกันด้วย
หากไม่จำเป็นต้องส่งพารามิเตอร์เพิ่มเติมไปยังเครื่องสถานะ ผู้ใช้สามารถเรียก T newStateMachine(S initialStateId)
เพื่อสร้างอินสแตนซ์เครื่องสถานะใหม่ได้
เครื่องสถานะใหม่จากผู้สร้างเครื่องของรัฐ (ในกรณีนี้ไม่จำเป็นต้องส่งพารามิเตอร์เพิ่มเติม)
MyStateMachine stateMachine = builder . newStateMachine ( MyState . Initial );
ทริกเกอร์การเปลี่ยนภาพ
หลังจากสร้างเครื่องสถานะแล้ว ผู้ใช้สามารถเริ่มเหตุการณ์พร้อมกับบริบทเพื่อทริกเกอร์การเปลี่ยนแปลงภายในเครื่องสถานะได้ เช่น
stateMachine . fire ( MyEvent . Prepare , new MyContext ( "Testing" ));
เครื่องสถานะที่ไม่ได้พิมพ์
เพื่อให้การใช้งานเครื่องสถานะง่ายขึ้น และหลีกเลี่ยงประเภททั่วไปมากเกินไป (เช่น StateMachine<T, S, E, C>) ซึ่งอาจทำให้โค้ดอ่านยากในบางกรณี แต่ยังคงรักษาส่วนสำคัญของคุณลักษณะความปลอดภัยของประเภทไว้ในการดำเนินการเปลี่ยน การดำเนินการ UntypedStateMachine ถูกนำมาใช้เพื่อจุดประสงค์นี้
enum TestEvent {
toA , toB , toC , toD
}
@ Transitions ({
@ Transit ( from = "A" , to = "B" , on = "toB" , callMethod = "fromAToB" ),
@ Transit ( from = "B" , to = "C" , on = "toC" ),
@ Transit ( from = "C" , to = "D" , on = "toD" )
})
@ StateMachineParameters ( stateType = String . class , eventType = TestEvent . class , contextType = Integer . class )
class UntypedStateMachineSample extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected UntypedStateMachineSample(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
protected void fromAToB ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
protected void transitFromDToAOntoA ( String from , String to , TestEvent event , Integer context ) {
// transition action still type safe ...
}
}
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (
UntypedStateMachineSample . class );
// state machine builder not type safe anymore
builder . externalTransition (). from ( "D" ). to ( "A" ). on ( TestEvent . toA );
UntypedStateMachine fsm = builder . newStateMachine ( "A" );
ในการสร้าง UntypedStateMachine ผู้ใช้จำเป็นต้องสร้าง UntypedStateMachineBuilder ผ่าน StateMachineBuilderFactory ก่อน StateMachineBuilderFactory ใช้พารามิเตอร์เดียวเท่านั้นซึ่งเป็นประเภทของคลาสเครื่องสถานะเพื่อสร้าง UntypedStateMachineBuilder @StateMachineParameters ใช้เพื่อประกาศประเภทพารามิเตอร์ทั่วไปของเครื่องสถานะ AbstractUntypedStateMachine เป็นคลาสพื้นฐานของเครื่องสถานะที่ไม่ได้พิมพ์ใดๆ
เครื่องสถานะที่ไม่คำนึงถึงบริบท
บางครั้งการเปลี่ยนสถานะไม่สนใจบริบท ซึ่งหมายความว่าการเปลี่ยนผ่านส่วนใหญ่จะถูกกำหนดโดยเหตุการณ์เท่านั้น สำหรับกรณีนี้ ผู้ใช้สามารถใช้เครื่องสถานะที่ไม่คำนึงถึงบริบทเพื่อลดความซับซ้อนของพารามิเตอร์การเรียกเมธอด ในการประกาศเครื่องสถานะที่ไม่คำนึงถึงบริบทนั้นค่อนข้างง่าย ผู้ใช้จำเป็นต้องเพิ่มคำอธิบายประกอบ @ContextInsensitive ในคลาสการใช้งานเครื่องสถานะเท่านั้น หลังจากนั้น คุณสามารถละเว้นพารามิเตอร์บริบทในรายการพารามิเตอร์วิธีการเปลี่ยนได้ เช่น
@ ContextInsensitive
public class ATMStateMachine extends AbstractStateMachine < ATMStateMachine , ATMState , String , Void > {
// no need to add context parameter here anymore
public void transitFromIdleToLoadingOnConnected ( ATMState from , ATMState to , String event ) {
...
}
public void entryLoading ( ATMState from , ATMState to , String event ) {
...
}
}
การจัดการข้อยกเว้นการเปลี่ยนแปลง
เมื่อมีข้อยกเว้นเกิดขึ้นระหว่างการเปลี่ยนสถานะ รายการการกระทำที่ดำเนินการจะถูกยกเลิก และเครื่องสถานะจะเข้าสู่สถานะข้อผิดพลาด ซึ่งหมายความว่าอินสแตนซ์เครื่องสถานะไม่สามารถประมวลผลเหตุการณ์ได้อีกต่อไป หากผู้ใช้ยังคงเริ่มเหตุการณ์ไปยังอินสแตนซ์ของเครื่องสถานะ IllegalStateException จะถูกโยนออกไป ข้อยกเว้นทั้งหมดที่เกิดขึ้นในระหว่างขั้นตอนการเปลี่ยนแปลง รวมถึงการดำเนินการและการเรียกใช้ Listener ภายนอกจะถูกรวมไว้ใน TransitionException (ข้อยกเว้นที่ไม่ได้ตรวจสอบ) ในปัจจุบัน กลยุทธ์การจัดการข้อยกเว้นเริ่มต้นนั้นเรียบง่ายและไม่สุภาพโดยเพียงแค่โยนข้อยกเว้นออกไป ดูวิธี AbstractStateMachine.afterTransitionCausedException
protected void afterTransitionCausedException (...) { throw e ; }
หากเครื่องสถานะสามารถกู้คืนได้จากข้อยกเว้นนี้ ผู้ใช้สามารถขยายวิธี afterTransitionCausedException และเพิ่มตรรกะการกู้คืนที่สอดคล้องกันในวิธีนี้ อย่า ลืมตั้งค่าสถานะของเครื่องให้กลับมาเป็นปกติในตอนท้าย เช่น
@ Override
protected void afterTransitionCausedException ( Object fromState , Object toState , Object event , Object context ) {
Throwable targeException = getLastException (). getTargetException ();
// recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'
if ( targeException instanceof IllegalArgumentException &&
fromState . equals ( "A" ) && toState . equals ( "B" ) && event . equals ( "ToB" )) {
// do some error clean up job here
// ...
// after recovered from this exception, reset the state machine status back to normal
setStatus ( StateMachineStatus . IDLE );
} else if (...) {
// recover from other exception ...
} else {
super . afterTransitionCausedException ( fromState , toState , event , context );
}
}
กำหนดสถานะลำดับชั้น
สถานะแบบลำดับชั้นอาจมีสถานะแบบซ้อนกัน รัฐเด็กอาจมีลูกซ้อนกันและการทำรังอาจไปลึกแค่ไหนก็ได้ เมื่อสถานะลำดับชั้นแอ็คทีฟ สถานะย่อยเพียงสถานะเดียวเท่านั้นที่จะแอ็คทีฟ สถานะลำดับชั้นสามารถกำหนดได้ผ่าน API หรือคำอธิบายประกอบ
void defineSequentialStatesOn ( S parentStateId , S ... childStateIds );
builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA) กำหนดสถานะย่อยสองสถานะ "BinA" และ "CinA" ภายใต้สถานะหลัก "A" สถานะย่อยแรกที่กำหนดจะเป็นสถานะเริ่มต้นของสถานะลำดับชั้น "A" . สถานะลำดับชั้นเดียวกันสามารถกำหนดได้ผ่านคำอธิบายประกอบ เช่น
@ States ({
@ State ( name = "A" , entryMethodCall = "entryA" , exitMethodCall = "exitA" ),
@ State ( parent = "A" , name = "BinA" , entryMethodCall = "entryBinA" , exitMethodCall = "exitBinA" , initialState = true ),
@ State ( parent = "A" , name = "CinA" , entryMethodCall = "entryCinA" , exitMethodCall = "exitCinA" )
})
กำหนดสถานะคู่ขนาน
สถานะคู่ขนานจะห่อหุ้มชุดของสถานะลูกที่ทำงานพร้อมกันเมื่อองค์ประกอบหลักทำงานอยู่ สถานะคู่ขนานสามารถกำหนดได้ผ่าน API หรือคำอธิบายประกอบทั้งสองอย่าง เช่น
// defines two region states "RegionState1" and "RegionState2" under parent parallel state "Root"
builder . defineParallelStatesOn ( MyState . Root , MyState . RegionState1 , MyState . RegionState2 );
builder . defineSequentialStatesOn ( MyState . RegionState1 , MyState . State11 , MyState . State12 );
builder . externalTransition (). from ( MyState . State11 ). to ( MyState . State12 ). on ( MyEvent . Event1 );
builder . defineSequentialStatesOn ( MyState . RegionState2 , MyState . State21 , MyState . State22 );
builder . externalTransition (). from ( MyState . State21 ). to ( MyState . State22 ). on ( MyEvent . Event2 );
หรือ
@ States ({
@ State ( name = "Root" , entryCallMethod = "enterRoot" , exitCallMethod = "exitRoot" , compositeType = StateCompositeType . PARALLEL ),
@ State ( parent = "Root" , name = "RegionState1" , entryCallMethod = "enterRegionState1" , exitCallMethod = "exitRegionState1" ),
@ State ( parent = "Root" , name = "RegionState2" , entryCallMethod = "enterRegionState2" , exitCallMethod = "exitRegionState2" )
})
เพื่อรับสถานะย่อยปัจจุบันของสถานะคู่ขนาน
stateMachine . getSubStatesOn ( MyState . Root ); // return list of current sub states of parallel state
เมื่อสถานะคู่ขนานทั้งหมดถึงสถานะสุดท้าย เหตุการณ์บริบท Finish จะถูกเริ่มทำงาน
กำหนดเหตุการณ์บริบท
เหตุการณ์บริบทหมายความว่าเหตุการณ์ที่ผู้ใช้กำหนดมีบริบทที่กำหนดไว้ล่วงหน้าในเครื่องสถานะ มูลนิธิกระรอกกำหนดเหตุการณ์บริบทสามประเภทสำหรับกรณีการใช้งานที่แตกต่างกัน เหตุการณ์เริ่มต้น/สิ้นสุด : เหตุการณ์ที่ประกาศเป็นเหตุการณ์เริ่มต้น/สิ้นสุดจะถูกใช้เมื่อเครื่องสถานะเริ่มต้น/สิ้นสุด ดังนั้นผู้ใช้สามารถแยกความแตกต่างของทริกเกอร์การดำเนินการที่ถูกเรียกใช้ได้ เช่น เมื่อเครื่องสถานะกำลังเริ่มต้นและเข้าสู่สถานะเริ่มต้น ผู้ใช้สามารถแยกแยะความแตกต่างของการดำเนินการรายการสถานะเหล่านี้ที่ถูกเรียกใช้โดยเหตุการณ์เริ่มต้นได้ เหตุการณ์ที่เสร็จสิ้น : เมื่อสถานะคู่ขนานทั้งหมดถึงสถานะสุดท้าย เหตุการณ์ที่เสร็จสิ้นจะถูกดำเนินการโดยอัตโนมัติ ผู้ใช้สามารถกำหนดการเปลี่ยนแปลงต่อไปนี้ตามเหตุการณ์ที่เสร็จสิ้น ในการกำหนดเหตุการณ์บริบท ผู้ใช้มีสองทาง คือ คำอธิบายประกอบหรือ API ตัวสร้าง
@ ContextEvent ( finishEvent = "Finish" )
static class ParallelStateMachine extends AbstractStateMachine <...> {
}
หรือ
StateMachineBuilder <...> builder = StateMachineBuilderFactory . create (...);
...
builder . defineFinishEvent ( HEvent . Start );
builder . defineTerminateEvent ( HEvent . Terminate );
builder . defineStartEvent ( HEvent . Finish );
การใช้สถานะประวัติเพื่อบันทึกและกู้คืนสถานะปัจจุบัน
สถานะหลอกของประวัติช่วยให้เครื่องสถานะจดจำการกำหนดค่าสถานะได้ การเปลี่ยนแปลงที่ใช้สถานะประวัติเป็นเป้าหมายจะทำให้เครื่องสถานะกลับสู่การกำหนดค่าที่บันทึกไว้นี้ หาก 'ประเภท' ของประวัติเป็น "ตื้น" โปรเซสเซอร์เครื่องสถานะจะต้องบันทึกรายการย่อยที่ใช้งานโดยตรงของรายการหลักก่อนที่จะทำการเปลี่ยนแปลงใด ๆ ที่ออกจากรายการหลัก หาก 'ประเภท' ของประวัติเป็น "ลึก" โปรเซสเซอร์เครื่องสถานะจะต้องบันทึกการสืบทอดที่ใช้งานอยู่ทั้งหมดของพาเรนต์ก่อนที่จะทำการเปลี่ยนแปลงใดๆ ที่ออกจากพาเรนต์ รองรับทั้ง API และคำอธิบายประกอบเพื่อกำหนดประเภทประวัติสถานะ เช่น
// defined history type of state "A" as "deep"
builder . defineSequentialStatesOn ( MyState . A , HistoryType . DEEP , MyState . A1 , MyState . A2 )
หรือ
@ State ( parent = "A" , name = "A1" , entryCallMethod = "enterA1" , exitCallMethod = "exitA1" , historyType = HistoryType . DEEP )
หมายเหตุ: ก่อน 0.3.7 ผู้ใช้จำเป็นต้องกำหนด "HistoryType.DEEP" สำหรับแต่ละระดับของสถานะทางประวัติศาสตร์ ซึ่งไม่สะดวกนัก (ขอบคุณ Voskuijlen ที่ให้แนวทางแก้ไข Issue33) ตอนนี้ผู้ใช้กำหนดเฉพาะ "HistoryType.DEEP" ที่ระดับบนสุดของสถานะทางประวัติศาสตร์ และรายการย่อยทั้งหมดระบุข้อมูลทางประวัติศาสตร์จะถูกจดจำ
ประเภทการเปลี่ยนผ่าน
ตามข้อกำหนด UML การเปลี่ยนแปลงอาจเป็นหนึ่งในสามประเภทนี้:
- การเปลี่ยนผ่านภายใน หมายความว่าการเปลี่ยนผ่าน (หากถูกกระตุ้น) จะเกิดขึ้นโดยไม่มีการออกหรือเข้าสู่สถานะต้นทาง (กล่าวคือ จะไม่ทำให้เกิดการเปลี่ยนแปลงสถานะ) ซึ่งหมายความว่าเงื่อนไขการเข้าหรือออกของสถานะต้นทางจะไม่ถูกเรียกใช้ การเปลี่ยนผ่านภายในสามารถทำได้แม้ว่า StateMachine จะอยู่ในหนึ่งหรือหลายภูมิภาคที่ซ้อนกันภายในรัฐที่เกี่ยวข้องก็ตาม
- การเปลี่ยนผ่านเฉพาะ ที่ หมายความว่าการเปลี่ยนผ่านหากถูกทริกเกอร์ จะไม่ออกจากสถานะคอมโพสิต (แหล่งที่มา) แต่จะออกและเข้าสู่สถานะใดๆ ภายในสถานะผสมที่อยู่ในการกำหนดค่าสถานะปัจจุบันอีกครั้ง
- การเปลี่ยนผ่านภายนอก หมายความว่าการเปลี่ยนผ่านหากถูกกระตุ้น จะออกจากสถานะผสม (แหล่งที่มา)
มูลนิธิกระรอกรองรับทั้ง API และคำอธิบายประกอบเพื่อประกาศการเปลี่ยนแปลงทุกประเภท เช่น
builder . externalTransition (). from ( MyState . A ). to ( MyState . B ). on ( MyEvent . A2B );
builder . internalTransition (). within ( MyState . A ). on ( MyEvent . innerA );
builder . localTransition (). from ( MyState . A ). to ( MyState . CinA ). on ( MyEvent . intoC )
หรือ
@ Transitions ({
@ Transition ( from = "A" , to = "B" , on = "A2B" ), //default value of transition type is EXTERNAL
@ Transition ( from = "A" , on = "innerA" , type = TransitionType . INTERNAL ),
@ Transition ( from = "A" , to = "CinA" , on = "intoC" , type = TransitionType . LOCAL ),
})
การจัดส่งเหตุการณ์ Polymorphism
ในระหว่างวงจรการใช้งานของเครื่องสถานะ เหตุการณ์ต่างๆ จะถูกเรียกใช้ เช่น
State Machine Lifecycle Events
|--StateMachineEvent /* Base event of all state machine event */
|--StartEvent /* Fired when state machine started */
|--TerminateEvent /* Fired when state machine terminated */
|--TransitionEvent /* Base event of all transition event */
|--TransitionBeginEvent /* Fired when transition began */
|--TransitionCompleteEvent /* Fired when transition completed */
|--TransitionExceptionEvent /* Fired when transition threw exception */
|--TransitionDeclinedEvent /* Fired when transition declined */
|--TransitionEndEvent /* Fired when transition end no matter declined or complete */
ผู้ใช้สามารถเพิ่ม Listener เพื่อฟัง StateMachineEvent ซึ่งหมายความว่าเหตุการณ์ทั้งหมดที่ดำเนินการในระหว่างวงจรการทำงานของเครื่องสถานะจะถูกดักฟังโดย Listener นี้ เช่น
stateMachine . addStateMachineListener ( new StateMachineListener <...>() {
@ Override
public void stateMachineEvent ( StateMachineEvent <...> event ) {
// ...
}
});
และ ผู้ใช้ยังสามารถเพิ่ม Listener เพื่อฟัง TransitionEvent ผ่าน StateMachine.addTransitionListener ซึ่งหมายความว่าเหตุการณ์ทั้งหมดที่เกิดขึ้นระหว่างการเปลี่ยนสถานะแต่ละครั้ง รวมถึง TransitionBeginEvent, TransitionCompleteEvent และ TransitionEndEvent จะถูกตรวจจับโดย Listener คนนี้ หรือ ผู้ใช้สามารถเพิ่มผู้ฟังที่เฉพาะเจาะจงเช่น TransitionDeclinedListener เพื่อฟัง TransitionDeclinedEvent เมื่อคำขอเปลี่ยนถูกปฏิเสธ
ผู้ฟังเหตุการณ์ที่ประกาศ
การเพิ่มตัวฟังเหตุการณ์ข้างต้นให้กับเครื่องสถานะบางครั้งอาจสร้างความรำคาญให้กับผู้ใช้ และประเภททั่วไปที่มากเกินไปก็ทำให้โค้ดอ่านยากเช่นกัน เพื่อให้การใช้งานเครื่องสถานะง่ายขึ้น สิ่งสำคัญยิ่งกว่าคือการจัดให้มีการบูรณาการแบบไม่รุกราน รากฐานกระรอกให้วิธีการประกาศในการเพิ่มผู้ฟังเหตุการณ์ผ่านคำอธิบายประกอบต่อไปนี้ เช่น
static class ExternalModule {
@ OnTransitionEnd
@ ListenerOrder ( 10 ) // Since 0.3.1 ListenerOrder can be used to insure listener invoked orderly
public void transitionEnd () {
// method annotated with TransitionEnd will be invoked when transition end...
// the method must be public and return nothing
}
@ OnTransitionBegin
public void transitionBegin ( TestEvent event ) {
// method annotated with TransitionBegin will be invoked when transition begin...
}
// 'event'(E), 'from'(S), 'to'(S), 'context'(C) and 'stateMachine'(T) can be used in MVEL scripts
@ OnTransitionBegin ( when = "event.name().equals( " toB " )" )
public void transitionBeginConditional () {
// method will be invoked when transition begin while transition caused by event "toB"
}
@ OnTransitionComplete
public void transitionComplete ( String from , String to , TestEvent event , Integer context ) {
// method annotated with TransitionComplete will be invoked when transition complete...
}
@ OnTransitionDecline
public void transitionDeclined ( String from , TestEvent event , Integer context ) {
// method annotated with TransitionDecline will be invoked when transition declined...
}
@ OnBeforeActionExecuted
public void onBeforeActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked before action invoked
}
@ OnAfterActionExecuted
public void onAfterActionExecuted ( Object sourceState , Object targetState ,
Object event , Object context , int [] mOfN , Action <?, ?, ?,?> action ) {
// method annotated with OnAfterActionExecuted will be invoked after action invoked
}
@ OnActionExecException
public void onActionExecException ( Action <?, ?, ?,?> action , TransitionException e ) {
// method annotated with OnActionExecException will be invoked when action thrown exception
}
}
ExternalModule externalModule = new ExternalModule ();
fsm . addDeclarativeListener ( externalModule );
...
fsm . removeDeclarativeListener ( externalModule );
การทำโค้ดโมดูลภายนอกนี้ไม่จำเป็นต้องใช้อินเทอร์เฟซ Listener ของเครื่องสถานะใดๆ เพิ่มคำอธิบายประกอบเพียงเล็กน้อยเกี่ยวกับวิธีการที่จะเชื่อมโยงระหว่างช่วงการเปลี่ยนภาพ พารามิเตอร์ของวิธีการยังเป็นประเภทที่ปลอดภัย และจะถูกอนุมานโดยอัตโนมัติเพื่อให้ตรงกับเหตุการณ์ที่เกี่ยวข้อง นี่เป็นแนวทางที่ดีใน การแยกข้อกังวล ผู้ใช้สามารถค้นหาการใช้งานตัวอย่างได้ใน org.squirrelframework.foundation.fsm.StateMachineLogger
วิธีการขยายช่วงการเปลี่ยนภาพ
เหตุการณ์การเปลี่ยนแปลงแต่ละรายการยังมีวิธีการขยายที่สอดคล้องกันในคลาส AbstractStateMachine ซึ่งได้รับอนุญาตให้ขยายในคลาสการใช้งานเครื่องสถานะของลูกค้า
protected void afterTransitionCausedException ( Exception e , S fromState , S toState , E event , C context ) {
}
protected void beforeTransitionBegin ( S fromState , E event , C context ) {
}
protected void afterTransitionCompleted ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionEnd ( S fromState , S toState , E event , C context ) {
}
protected void afterTransitionDeclined ( S fromState , E event , C context ) {
}
protected void beforeActionInvoked ( S fromState , S toState , E event , C context ) {
}
โดยทั่วไปแล้ว ผู้ใช้สามารถเชื่อมโยงตรรกะการประมวลผลธุรกิจของคุณเข้ากับวิธีการขยายเหล่านี้ในระหว่างการเปลี่ยนสถานะแต่ละครั้ง ในขณะที่ตัวฟังเหตุการณ์ต่างๆ ทำหน้าที่เป็นขอบเขตของระบบควบคุมที่ใช้เครื่องสถานะ ซึ่งสามารถโต้ตอบกับโมดูลภายนอก (เช่น UI, การตรวจสอบ, ESB และอื่นๆ ). ตัวอย่างเช่น ผู้ใช้สามารถขยายวิธีการ afterTransitionCausedException สำหรับการล้างสภาพแวดล้อมเมื่อมีข้อยกเว้นเกิดขึ้นระหว่างการเปลี่ยนแปลง และยังแจ้งโมดูลอินเทอร์เฟซผู้ใช้ให้แสดงข้อความแสดงข้อผิดพลาดผ่าน TransitionExceptionEvent
การกระทำแบบถ่วงน้ำหนัก
ผู้ใช้สามารถกำหนดน้ำหนักการดำเนินการเพื่อปรับลำดับการดำเนินการได้ การดำเนินการระหว่างการเข้า/ออกสถานะและการเปลี่ยนสถานะจะเรียงลำดับจากน้อยไปหามากตามค่าน้ำหนัก น้ำหนักการดำเนินการคือ 0 โดยค่าเริ่มต้น ผู้ใช้มีสองวิธีในการกำหนดน้ำหนักการกระทำ
หนึ่งคือเพิ่มหมายเลขน้ำหนักต่อท้ายชื่อวิธีการและคั่นด้วย ':'
// define state entry action 'goEntryD' weight -150
@ State ( name = "D" , entryCallMethod = "goEntryD:-150" )
// define transition action 'goAToC1' weight +150
@ Transit ( from = "A" , to = "C" , on = "ToC" , callMethod = "goAToC1:+150" )
อีกวิธีหนึ่งคือการแทนที่วิธีน้ำหนักของคลาส Action เช่น
Action <...> newAction = new Action <...>() {
...
@ Override
public int weight () {
return 100 ;
}
}
มูลนิธิกระรอกยังสนับสนุนลักษณะทั่วไปในการประกาศน้ำหนักการกระทำ น้ำหนักของการดำเนินการเรียกเมธอดที่ชื่อขึ้นต้นด้วย ' ก่อน ' จะถูกตั้งค่าเป็น 100 ดังนั้นเมื่อชื่อขึ้นต้นด้วย ' หลัง ' จะถูกตั้งค่าเป็น -100 โดยทั่วไปหมายความว่าชื่อวิธีดำเนินการที่ขึ้นต้นด้วย 'before' จะถูกเรียกใช้ในตอนแรก ในขณะที่ชื่อวิธีดำเนินการที่ขึ้นต้นด้วย 'after' จะถูกเรียกใช้ในที่สุด "method1:ignore" หมายความว่า method1 จะไม่ถูกเรียกใช้
สำหรับข้อมูลเพิ่มเติม โปรดดูกรณีทดสอบ ' org.squirrelframework.foundation.fsm.WeightedActionTest ';
การดำเนินการแบบอะซิงโครไนซ์
คำอธิบายประกอบ @AsyncExecute สามารถใช้กับการดำเนินการการเรียกเมธอดและตัวฟังเหตุการณ์ที่ประกาศเพื่อระบุว่าการกระทำหรือตัวฟังเหตุการณ์นี้จะถูกดำเนินการแบบอะซิงโครนัส เช่น กำหนดวิธีการดำเนินการที่เรียกใช้แบบอะซิงโครนัส:
@ ContextInsensitive
@ StateMachineParameters ( stateType = String . class , eventType = String . class , contextType = Void . class )
public class ConcurrentSimpleStateMachine extends AbstractUntypedStateMachine {
// No need to specify constructor anymore since 0.2.9
// protected ConcurrentSimpleStateMachine(ImmutableUntypedState initialState,
// Map<Object, ImmutableUntypedState> states) {
// super(initialState, states);
// }
@ AsyncExecute
protected void fromAToB ( String from , String to , String event ) {
// this action method will be invoked asynchronously
}
}
กำหนดเหตุการณ์ที่ส่งแบบอะซิงโครนัส:
public class DeclarativeListener {
@ OnTransitionBegin
@ AsyncExecute
public void onTransitionBegin (...) {
// transition begin event will be dispatched asynchronously to this listener method
}
}
งานการดำเนินการแบบอะซิงโครนัสจะถูกส่งไปยัง ExecutorService ผู้ใช้สามารถลงทะเบียนอินสแตนซ์การใช้งาน ExecutorService ของคุณผ่าน SquirrelSingletonProvider เช่น
ExecutorService executorService = Executors . newFixedThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ExecutorService . class , executorService );
หากไม่มีการลงทะเบียนอินสแตนซ์ ExecutorService SquirrelConfiguration จะจัดเตรียมอินสแตนซ์เริ่มต้นไว้
เครื่องสถานะ PostProcessor
ผู้ใช้สามารถลงทะเบียน post processor สำหรับ state machine บางประเภทได้ เพื่อเพิ่ม post process logic หลังจาก state machine เริ่มทำงาน เช่น
// 1 User defined a state machine interface
interface MyStateMachine extends StateMachine < MyStateMachine , MyState , MyEvent , MyContext > {
. . .
}
// 2 Both MyStateMachineImpl and MyStateMachineImplEx are implemented MyStateMachine
class MyStateMachineImpl implements MyStateMachine {
. . .
}
class MyStateMachineImplEx implements MyStateMachine {
. . .
}
// 3 User define a state machine post processor
MyStateMachinePostProcessor implements SquirrelPostProcessor < MyStateMachine > {
void postProcess ( MyStateMachine component ) {
. . .
}
}
// 4 User register state machine post process
SquirrelPostProcessorProvider . getInstance (). register ( MyStateMachine . class , MyStateMachinePostProcessor . class );
ในกรณีนี้ เมื่อผู้ใช้สร้างทั้งอินสแตนซ์ MyStateMachineImpl และ MyStateMachineImplEx ตัวประมวลผลที่ลงทะเบียน MyStateMachinePostProcessor จะถูกเรียกให้ทำงานบางอย่าง
การส่งออกเครื่องจักรของรัฐ
SCXMLVisitor สามารถใช้เพื่อส่งออกคำจำกัดความของเครื่องสถานะในเอกสาร [SCXML] 2
SCXMLVisitor visitor = SquirrelProvider . getInstance (). newInstance ( SCXMLVisitor . class );
stateMachine . accept ( visitor );
visitor . convertSCXMLFile ( "MyStateMachine" , true );
BTW ผู้ใช้ยังสามารถเรียก StateMachine.exportXMLDefinition(true) เพื่อส่งออกคำจำกัดความ XML ที่สวยงามได้ DotVisitor สามารถใช้เพื่อสร้างแผนภาพสถานะซึ่งสามารถดูได้โดย [GraphViz] 3
DotVisitor visitor = SquirrelProvider . getInstance (). newInstance ( DotVisitor . class );
stateMachine . accept ( visitor );
visitor . convertDotFile ( "SnakeStateMachine" );
การนำเข้าเครื่องจักรของรัฐ
UntypedStateMachineImporter สามารถใช้เพื่อนำเข้าข้อกำหนดที่คล้ายกันของ SCXML ของเครื่องสถานะซึ่งส่งออกโดย SCXMLVisitor หรือข้อกำหนดการเขียนด้วยลายมือ UntypedStateMachineImporter จะสร้าง UntypedStateMachineBuilder ตามคำจำกัดความ ซึ่งสามารถนำมาใช้สร้างอินสแตนซ์ของเครื่องสถานะได้ในภายหลัง
UntypedStateMachineBuilder builder = new UntypedStateMachineImporter (). importDefinition ( scxmlDef );
ATMStateMachine stateMachine = builder . newAnyStateMachine ( ATMState . Idle );
หมายเหตุ: UntypedStateMachineImporter จัดเตรียมรูปแบบ XML เพื่อกำหนดเครื่องสถานะเช่นเดียวกับ API ของตัวสร้างเครื่องสถานะหรือคำอธิบายประกอบที่ประกาศ คำจำกัดความที่คล้ายกันของ SCXML ไม่เท่ากับ SCXML มาตรฐาน
บันทึก/โหลดข้อมูลเครื่องสถานะ
ผู้ใช้สามารถบันทึกข้อมูลของเครื่องสถานะได้เมื่อเครื่องสถานะอยู่ในสถานะไม่ได้ใช้งาน
StateMachineData . Reader < MyStateMachine , MyState , MyEvent , MyContext >
savedData = stateMachine . dumpSavedData ();
และผู้ใช้สามารถโหลด ข้อมูลที่บันทึก ไว้ข้างต้นลงในเครื่องสถานะอื่นที่มีสถานะถูกยกเลิกหรือเพิ่งเริ่มต้นได้
newStateMachineInstance . loadSavedData ( savedData );
หมายเหตุ : ข้อมูลเครื่องสถานะสามารถซีเรียลไลซ์เป็น/ดีซีเรียลไลซ์ได้จากสตริงที่เข้ารหัส Base64 ด้วยความช่วยเหลือของคลาส ObjectSerializableSupport
การกำหนดค่าเครื่องสถานะ
เมื่อสร้างอินสแตนซ์เครื่องสถานะใหม่ ผู้ใช้สามารถกำหนดค่าลักษณะการทำงานผ่าน StateMachineConfiguration เช่น
UntypedStateMachine fsm = builder . newUntypedStateMachine ( "a" ,
StateMachineConfiguration . create (). enableAutoStart ( false )
. setIdProvider ( IdProvider . UUIDProvider . getInstance ()),
new Object [ 0 ]); // since 0.3.0
fsm . fire ( TestEvent . toA );
โค้ดตัวอย่างด้านบนใช้เพื่อสร้างอินสแตนซ์เครื่องสถานะโดยมี UUID เป็นตัวระบุ และปิดใช้งานฟังก์ชันเริ่มต้นอัตโนมัติ StateMachineConfigure ยังสามารถตั้งค่าบนตัวสร้างเครื่องสถานะได้ ซึ่งหมายความว่าอินสแตนซ์เครื่องสถานะทั้งหมดที่สร้างโดย builder.newStateMachine(S initialStateId)
หรือ builder.newStateMachine(S initialStateId, Object... extraParams)
จะใช้การกำหนดค่านี้
การวินิจฉัยเครื่องของรัฐ
StateMachineLogger ใช้เพื่อสังเกตสถานะภายในของเครื่องสถานะ เช่น ประสิทธิภาพการดำเนินการ ลำดับการเรียกการดำเนินการ ความคืบหน้าของการเปลี่ยนแปลง และอื่นๆ เช่น
StateMachine <?,?,?,?> stateMachine = builder . newStateMachine ( HState . A );
StateMachineLogger fsmLogger = new StateMachineLogger ( stateMachine );
fsmLogger . startLogging ();
...
stateMachine . fire ( HEvent . B2A , 1 );
...
fsmLogger . terminateLogging ();
-------------------------------------------------------------------------------------------
Console Log :
HierachicalStateMachine : Transition from "B2a" on "B2A" with context "1" begin .
Before execute method call action "leftB2a" ( 1 of 6 ).
Before execute method call action "exitB2" ( 2 of 6 ).
...
Before execute method call action "entryA1" ( 6 of 6 ).
HierachicalStateMachine : Transition from "B2a" to "A1" on "B2A" complete which took 2 ms .
...
เนื่องจากเครื่องบันทึกสถานะ v0.3.0 สามารถใช้งานได้ง่ายขึ้นโดยเพียงแค่ตั้งค่า StateMachineConfiguration เปิดใช้งานโหมดแก้ไขข้อบกพร่อง เช่น
StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A,
StateMachineConfiguration.create().enableDebugMode(true),
new Object[0]);
StateMachinePerformanceMonitor สามารถใช้ในการตรวจสอบข้อมูลประสิทธิภาพการทำงานของเครื่องสถานะ รวมถึงจำนวนครั้งในการเปลี่ยนแปลงทั้งหมด เวลาที่ใช้การเปลี่ยนแปลงโดยเฉลี่ย และอื่นๆ เช่น
final UntypedStateMachine fsm = builder . newStateMachine ( "D" );
final StateMachinePerformanceMonitor performanceMonitor =
new StateMachinePerformanceMonitor ( "Sample State Machine Performance Info" );
fsm . addDeclarativeListener ( performanceMonitor );
for ( int i = 0 ; i < 10000 ; i ++) {
fsm . fire ( FSMEvent . ToA , 10 );
fsm . fire ( FSMEvent . ToB , 10 );
fsm . fire ( FSMEvent . ToC , 10 );
fsm . fire ( FSMEvent . ToD , 10 );
}
fsm . removeDeclarativeListener ( performanceMonitor );
System . out . println ( performanceMonitor . getPerfModel ());
-------------------------------------------------------------------------------------------
Console Log :
========================== Sample State Machine Performance Info ==========================
Total Transition Invoked : 40000
Total Transition Failed : 0
Total Transition Declained : 0
Average Transition Comsumed : 0.0004 ms
Transition Key Invoked Times Average Time Max Time Min Time
C --{ ToD , 10 }-> D 10000 0.0007 ms 5 ms 0 ms
B --{ ToC , 10 }-> C 10000 0.0001 ms 1 ms 0 ms
D --{ ToA , 10 }-> A 10000 0.0009 ms 7 ms 0 ms
A --{ ToB , 10 }-> B 10000 0.0000 ms 1 ms 0 ms
Total Action Invoked : 40000
Total Action Failed : 0
Average Action Execution Comsumed : 0.0000 ms
Action Key Invoked Times Average Time Max Time Min Time
instan ... Test$1 40000 0.0000 ms 1 ms 0 ms
========================== Sample State Machine Performance Info ==========================
เพิ่ม @LogExecTime ในวิธีการดำเนินการจะออกจากระบบเวลาดำเนินการของวิธีการ และยังเพิ่ม @LogExecTime ในคลาสเครื่องสถานะจะออกจากระบบเวลาดำเนินการของวิธีการกระทำทั้งหมด ตัวอย่างเช่น เวลาดำเนินการของวิธี TransitFromAToBOnGoToB จะถูกล็อกเอาต์
@ LogExecTime
protected void transitFromAToBOnGoToB ( MyState from , MyState to , MyEvent event , MyContext context )
สถานะหมดเวลา
สถานะที่กำหนดเวลา คือสถานะที่สามารถหน่วงเวลาหรือทริกเกอร์เหตุการณ์ที่ระบุเป็นระยะๆ หลังจากที่เข้าสู่สถานะ งานที่กำหนดเวลาไว้จะถูกส่งไปยัง ScheduledExecutorService ผู้ใช้สามารถลงทะเบียนอินสแตนซ์การใช้งาน ScheduledExecutorService ของคุณผ่าน SquirrelSingletonProvider เช่น
ScheduledExecutorService scheduler = Executors . newScheduledThreadPool ( 1 );
SquirrelSingletonProvider . getInstance (). register ( ScheduledExecutorService . class , scheduler );
หากไม่มีการลงทะเบียนอินสแตนซ์ ScheduledExecutorService SquirrelConfiguration จะจัดเตรียมอินสแตนซ์เริ่มต้นไว้ หลังจากนั้น สถานะที่กำหนดเวลาไว้สามารถกำหนดได้โดยผู้สร้างเครื่องสถานะ เช่น
// after 50ms delay fire event "FIRST" every 100ms with null context
builder . defineTimedState ( "A" , 50 , 100 , "FIRST" , null );
builder . internalTransition (). within ( "A" ). on ( "FIRST" );
หมายเหตุ : ตรวจสอบให้แน่ใจว่าต้องกำหนดสถานะตามเวลาก่อนที่จะอธิบายการเปลี่ยนหรือการดำเนินการเข้า/ออก timeInterval น้อยกว่าหรือเท่ากับ 0 จะถือว่าดำเนินการเพียงครั้งเดียวหลังจาก InitialDelay
สถานะที่เชื่อมโยง (เรียกว่าสถานะเครื่องย่อย)
สถานะที่เชื่อมโยง ระบุการแทรกข้อกำหนดของเครื่องสถานะเครื่องย่อย เครื่องสถานะที่มีสถานะที่เชื่อมโยงเรียกว่าเครื่องสถานะที่มี เครื่องสถานะเดียวกันอาจเป็นเครื่องย่อยมากกว่าหนึ่งครั้งในบริบทของเครื่องเดียวที่มีเครื่องสถานะ
สถานะที่เชื่อมโยงมีความหมายเทียบเท่ากับสถานะแบบผสม ขอบเขตของเครื่องสถานะเครื่องย่อยคือขอบเขตของสถานะคอมโพสิต การดำเนินการเข้า ออก และพฤติกรรมและการเปลี่ยนภายในถูกกำหนดให้เป็นส่วนหนึ่งของสถานะ สถานะของเครื่องจักรย่อยเป็นกลไกการสลายตัวที่ช่วยให้แยกตัวประกอบพฤติกรรมทั่วไปและการนำกลับมาใช้ใหม่ได้ สถานะที่เชื่อมโยงสามารถกำหนดได้โดยทำตามโค้ดตัวอย่างต่อไปนี้
builderOfTestStateMachine . definedLinkedState ( LState . A , builderOfLinkedStateMachine , LState . A1 );
การสนับสนุน JMX
ตั้งแต่ 0.3.3 ผู้ใช้สามารถมอนิเตอร์สถานะเครื่องระยะไกล (เช่น สถานะปัจจุบัน ชื่อ) และแก้ไขการกำหนดค่า (เช่น การบันทึกสลับ/สลับการตรวจสอบประสิทธิภาพ/เหตุการณ์ไฟไหม้ระยะไกล) ขณะรันไทม์ ข้อมูลอินสแตนซ์ของเครื่องสถานะทั้งหมดจะอยู่ภายใต้โดเมน "org.squirrelframework" โค้ดตัวอย่างต่อไปนี้แสดงวิธีเปิดใช้งานการสนับสนุน JMX
UntypedStateMachineBuilder builder = StateMachineBuilderFactory . create (...);
builder . setStateMachineConfiguration ( StateMachineConfiguration . create (). enableRemoteMonitor ( true ));
หมายเหตุ : การสนับสนุนคุณลักษณะ JMX เลิกใช้แล้วตั้งแต่ 0.3.9-SNAPSHOT
ดูไฟล์ตัวอย่าง
ดูไฟล์บันทึกการเผยแพร่
หากต้องการทราบ ข้อมูลอัปเดตล่าสุด โปรดติดตาม Twitter ของฉัน @hhe11 หรือ +HeHenry
สำหรับการสนทนาหรือคำถามกรุณาเข้าร่วมกลุ่มเครื่องสถานะกระรอก
สำหรับปัญหาหรือความต้องการใด ๆ โปรดส่งปัญหา
หากคุณใช้รหัส Squirrel State Machine ในใบสมัครของคุณ เราจะยินดีอย่างยิ่งหากคุณแจ้งให้ผู้เขียนทราบ (อีเมล: [email protected]) เช่นนี้ :
เรื่อง: ข้อความแจ้งเตือนการใช้งานเครื่อง Squirrel State: ฉันใช้ Squirrel State Machine <lib_version> ใน <project_name> - http://link_to_project. ฉัน [อนุญาต | ไม่อนุญาตให้] พูดถึงโครงการของฉันในส่วน "ใครใช้ Squirrel State Machine" บน GitHub