คอมพิวเตอร์ หน้าต่าง อินเทอร์เน็ต

สถานะ. ชั้นเรียนของรัฐสำหรับแต่ละรัฐ

รูปแบบการออกแบบพฤติกรรม มันถูกใช้ในกรณีที่ระหว่างการทำงานของโปรแกรม อ็อบเจ็กต์ต้องเปลี่ยนพฤติกรรมของมันขึ้นอยู่กับสถานะของมัน การใช้งานแบบคลาสสิกเกี่ยวข้องกับการสร้างคลาสนามธรรมพื้นฐานหรืออินเทอร์เฟซที่มีวิธีการทั้งหมดและหนึ่งคลาสสำหรับแต่ละสถานะที่เป็นไปได้ รูปแบบนี้เป็นกรณีพิเศษของคำแนะนำ "แทนที่ข้อความสั่งแบบมีเงื่อนไขด้วยความหลากหลาย"

ดูเหมือนว่าทุกอย่างเป็นไปตามหนังสือ แต่มีความแตกต่างกันนิดหน่อย จะใช้วิธีการที่ถูกต้องที่ไม่เกี่ยวข้องกับสถานะที่กำหนดได้อย่างไร ตัวอย่างเช่น จะลบสินค้าออกจากรถเข็นเปล่าหรือชำระค่ารถเข็นเปล่าได้อย่างไร? โดยทั่วไป แต่ละคลาสของรัฐใช้วิธีการที่เกี่ยวข้องเท่านั้น และจะส่ง InvalidOperationException ในกรณีอื่น ๆ

การละเมิดหลักการทดแทน Liskov สำหรับบุคคล Yaron Minsky เสนอแนวทางอื่น: ทำให้รัฐที่ผิดกฎหมายไม่สามารถเป็นตัวแทนได้. ซึ่งทำให้สามารถย้ายการตรวจสอบข้อผิดพลาดจากรันไทม์ไปเป็นเวลาคอมไพล์ได้ อย่างไรก็ตาม ขั้นตอนการควบคุมในกรณีนี้จะถูกจัดระเบียบตามการจับคู่รูปแบบ และไม่ใช้ความหลากหลาย โชคดี, .

รายละเอียดเพิ่มเติมเกี่ยวกับตัวอย่างหัวข้อ F# ทำให้รัฐที่ผิดกฎหมายไม่สามารถเป็นตัวแทนได้เปิดเผยบนเว็บไซต์ของ Scott Vlashin

ลองพิจารณาการนำ "สถานะ" ไปใช้โดยใช้ตัวอย่างของตะกร้า C# ไม่มีประเภทยูเนี่ยนในตัว มาแยกข้อมูลและพฤติกรรมกันดีกว่า เราจะเข้ารหัสสถานะโดยใช้ enum และพฤติกรรมเป็นคลาสที่แยกจากกัน เพื่อความสะดวก เราจะประกาศแอตทริบิวต์ที่เชื่อมต่อ enum และคลาสพฤติกรรมที่สอดคล้องกัน คลาส "สถานะ" พื้นฐาน และเพิ่มวิธีการขยายเพื่อย้ายจาก enum ไปยังคลาสพฤติกรรม

โครงสร้างพื้นฐาน

StateAttribute คลาสสาธารณะ: คุณสมบัติ ( public Type StateType ( get; ) public StateAttribute(Type stateType) ( StateType = stateType ?? โยนใหม่ ArgumentNullException(nameof(stateType)); ) ) คลาสนามธรรมสาธารณะ State โดยที่ T: คลาส ( สถานะที่ได้รับการป้องกัน (เอนทิตี T) ( เอนทิตี = เอนทิตี ?? โยน ArgumentNullException ใหม่ (ชื่อ (เอนทิตี)); ) เอนทิตี T ที่ได้รับการป้องกัน ( รับ; ) ) คลาสคงที่สาธารณะ StateCodeExtensions ( สถานะคงที่สาธารณะ สู่รัฐ (Enum stateCode นี้ เอนทิตีวัตถุ) โดยที่ T: class // ใช่ ใช่ การสะท้อนกลับช้า แทนที่ด้วยแผนผังนิพจน์ที่คอมไพล์ // หรือ IL Emit แล้วมันจะเร็ว => (State ) Activator.CreateInstance(stateCode .GetType() .GetCustomAttribute ().StateType เอนทิตี); )

สาขาวิชา

มาประกาศเอนทิตี "รถเข็น":

อินเทอร์เฟซสาธารณะ IHasState โดยที่ TEntity: คลาส ( TStateCode StateCode (get; ) สถานะ สถานะ (get; ) ) รถเข็นคลาสสาธารณะบางส่วน: IHasState ( ผู้ใช้สาธารณะ ผู้ใช้ (get; ชุดป้องกัน; ) CartStateCode สาธารณะ StateCode (get; ชุดป้องกัน; ) รัฐสาธารณะ รัฐ => StateCode.ToState (นี้); ทศนิยมสาธารณะรวม (get; ชุดป้องกัน; ) ICollection เสมือนที่ได้รับการป้องกัน สินค้า (get; set; ) = new List (); // ORM ป้องกันเฉพาะรถเข็น () ( ) รถเข็นสาธารณะ (ผู้ใช้ผู้ใช้) ( User = user ?? โยน ArgumentNullException ใหม่ (ชื่อ (ผู้ใช้)); StateCode = StateCode = CartStateCode.Empty; ) รถเข็นสาธารณะ (ผู้ใช้ผู้ใช้ IEnumerable ผลิตภัณฑ์): this(user) ( StateCode = StateCode = CartStateCode.Empty; foreach (var product in products) ( Products.Add(product); ) ) public Cart(User user, IEnumerable ผลิตภัณฑ์, ผลรวมทศนิยม) : นี่(ผู้ใช้, ผลิตภัณฑ์) ( if (total<= 0) { throw new ArgumentException(nameof(total)); } Total = total; } }
เราจะใช้หนึ่งคลาสสำหรับสถานะรถเข็นแต่ละรัฐ: ว่างเปล่า ใช้งานอยู่ และชำระเงินแล้ว แต่เราจะไม่ประกาศอินเทอร์เฟซทั่วไป ปล่อยให้แต่ละรัฐใช้เฉพาะพฤติกรรมที่เกี่ยวข้องเท่านั้น นี่ไม่ได้หมายความว่าคลาส EmptyCartState, ActiveCartState และ PaidCartState ไม่สามารถใช้อินเทอร์เฟซเดียวกันทั้งหมดได้ สามารถทำได้ แต่อินเทอร์เฟซดังกล่าวต้องมีเฉพาะวิธีการที่มีอยู่ในแต่ละสถานะเท่านั้น ในกรณีของเรา วิธีการเพิ่มมีอยู่ใน EmptyCartState และ ActiveCartState ดังนั้นเราจึงสามารถสืบทอดวิธีการเหล่านี้จาก AddableCartStateBase แบบนามธรรม อย่างไรก็ตาม คุณสามารถเพิ่มสินค้าลงในรถเข็นที่ยังไม่ได้ชำระเงินได้เท่านั้น ดังนั้นจึงไม่มีอินเทอร์เฟซทั่วไปสำหรับทุกรัฐ ด้วยวิธีนี้เรารับประกันได้ว่าไม่มี InvalidOperationException ในโค้ดของเราในขณะคอมไพล์

รถเข็นคลาสสาธารณะบางส่วน (สาธารณะ enum CartStateCode: ไบต์ (ว่างเปล่า, ใช้งานอยู่, ชำระเงิน) อินเทอร์เฟซสาธารณะ IAddableCartState ( ActiveCartState เพิ่ม (ผลิตภัณฑ์ผลิตภัณฑ์); IEnumerable ผลิตภัณฑ์ (get; ) ) อินเทอร์เฟซสาธารณะ INotEmptyCartState ( IEnumerable ผลิตภัณฑ์ (get; ) ทศนิยมรวม (get; ) ) คลาสนามธรรมสาธารณะ AddableCartState: State , IAddableCartState ( ป้องกัน AddableCartState(เอนทิตีรถเข็น): ฐาน(เอนทิตี) ( ) สาธารณะ ActiveCartState เพิ่ม(ผลิตภัณฑ์ผลิตภัณฑ์) ( Entity.Products.Add(product); Entity.StateCode = CartStateCode.Active; return (ActiveCartState)Entity.State; ) IEnumerable สาธารณะ ผลิตภัณฑ์ => เอนทิตี. ผลิตภัณฑ์; ) คลาสสาธารณะ EmptyCartState: AddableCartState ( สาธารณะ EmptyCartState (เอนทิตีรถเข็น): ฐาน (เอนทิตี) ( ) ) คลาสสาธารณะ ActiveCartState: AddableCartState, INotEmptyCartState ( สาธารณะ ActiveCartState (เอนทิตีรถเข็น): ฐาน (เอนทิตี) ( ) สาธารณะ PaidCartState Pay (ผลรวมทศนิยม) ( Entity.Total = ผลรวม; Entity.StateCode = CartStateCode.Paid; return (PaidCartState)Entity.State; ) รัฐสาธารณะ Remove(Product product) ( Entity.Products.Remove(product); if(!Entity.Products.Any()) ( Entity.StateCode = CartStateCode.Empty; ) ส่งคืน Entity.State; ) public EmptyCartState Clear() ( Entity. Products.Clear(); Entity.StateCode = CartStateCode.Empty; return (EmptyCartState)Entity.State; ) ผลรวมทศนิยมสาธารณะ => Products.Sum(x => x.Price); ) PaidCartState คลาสสาธารณะ: รัฐ , INotEmptyCartState ( IEnumerable. สาธารณะ ผลิตภัณฑ์ => เอนทิตี. ผลิตภัณฑ์; รวมทศนิยมสาธารณะ => Entity.Total; PaidCartState สาธารณะ (เอนทิตีรถเข็น) : ฐาน (เอนทิตี) ( ) ) )
รัฐถูกประกาศว่าซ้อนกัน ( ซ้อนกัน) ชั้นเรียนไม่ใช่เรื่องบังเอิญ คลาสที่ซ้อนกันสามารถเข้าถึงสมาชิกที่ได้รับการป้องกันของคลาส Cart ซึ่งหมายความว่าเราไม่จำเป็นต้องเสียสละการห่อหุ้มเอนทิตีเพื่อนำพฤติกรรมไปใช้ เพื่อหลีกเลี่ยงความยุ่งเหยิงในไฟล์คลาสเอนทิตี ฉันจึงแยกการประกาศออกเป็นสองส่วน: Cart.cs และ CartStates.cs โดยใช้คีย์เวิร์ดบางส่วน

ผลการดำเนินการสาธารณะ GetViewResult (สถานะ cartState) ( สวิตช์ (cartState) ( case Cart.ActiveCartState activeState: return View("Active", activeState); case Cart.EmptyCartState EmptyState: return View("Empty", EmptyState); case Cart.PaidCartState PaidCartState: return View(" จ่ายแล้ว", PaidCartState); ค่าเริ่มต้น: โยน InvalidOperationException(); ) ) ใหม่
เราจะใช้มุมมองที่แตกต่างกันขึ้นอยู่กับสถานะของรถเข็น สำหรับรถเข็นเปล่า เราจะแสดงข้อความ “รถเข็นของคุณว่างเปล่า” รถเข็นที่ใช้งานอยู่จะมีรายการผลิตภัณฑ์ ความสามารถในการเปลี่ยนจำนวนผลิตภัณฑ์และลบบางส่วนออก ปุ่ม "สั่งซื้อ" และจำนวนการซื้อทั้งหมด

รถเข็นที่ชำระเงินจะมีลักษณะเหมือนกับรถเข็นที่ใช้งานอยู่ แต่ไม่มีความสามารถในการแก้ไขสิ่งใดๆ ข้อเท็จจริงนี้สามารถสังเกตได้โดยการเน้นอินเทอร์เฟซ INotEmptyCartState ดังนั้นเราจึงไม่เพียง แต่กำจัดการละเมิดหลักการทดแทน Liskov เท่านั้น แต่ยังใช้หลักการของการแยกส่วนต่อประสานด้วย

บทสรุป

ในรหัสแอปพลิเคชัน เราสามารถทำงานร่วมกับลิงก์อินเทอร์เฟซ IAddableCartState และ INotEmptyCartState เพื่อใช้รหัสที่รับผิดชอบในการเพิ่มสินค้าลงในรถเข็นและแสดงรายการในรถเข็นอีกครั้ง ฉันเชื่อว่าการจับคู่รูปแบบเหมาะสำหรับโฟลว์การควบคุมใน C# เท่านั้น เมื่อไม่มีอะไรเหมือนกันระหว่างประเภทต่างๆ ในกรณีอื่นๆ การทำงานกับลิงก์ฐานจะสะดวกกว่า เทคนิคที่คล้ายกันนี้สามารถใช้ได้ไม่เพียงแต่ในการเข้ารหัสพฤติกรรมของเอนทิตีเท่านั้น แต่ยังสำหรับ .

ถึงเวลาที่ต้องสารภาพ: ฉันพูดเกินจริงไปหน่อยกับเรื่องหลักนี้ มันควรจะเกี่ยวกับรูปแบบการออกแบบของ GoF State แต่ฉันไม่สามารถพูดถึงการใช้งานในเกมโดยไม่ต้องพูดถึงคอนเซ็ปต์ได้ เครื่องจักรสถานะจำกัด(หรือ "FSM") แต่เมื่อฉันเข้าใจแล้ว ฉันก็รู้ว่าฉันต้องจำมันให้ได้ เครื่องสถานะแบบลำดับชั้นหรือ หุ่นยนต์แบบลำดับชั้นและ เครื่องอัตโนมัติพร้อมหน่วยความจำนิตยสาร (ออโตเมต้าแบบกดลง).

นี่เป็นหัวข้อที่กว้างมาก ดังนั้นเพื่อให้บทนี้สั้นที่สุดเท่าที่จะเป็นไปได้ ฉันจะละตัวอย่างโค้ดที่ชัดเจนบางส่วนไว้ และคุณจะต้องกรอกข้อมูลในช่องว่างบางส่วนด้วยตนเอง ฉันหวังว่านี่จะไม่ทำให้พวกเขาเข้าใจน้อยลง

ไม่จำเป็นต้องอารมณ์เสียหากคุณไม่เคยได้ยินเกี่ยวกับเครื่องจักรที่มีสถานะจำกัดมาก่อน พวกเขาเป็นที่รู้จักดีในหมู่นักพัฒนา AI และแฮกเกอร์คอมพิวเตอร์ แต่ไม่ค่อยมีใครรู้จักในด้านอื่นๆ ในความคิดของฉัน พวกเขาสมควรได้รับการยอมรับมากกว่านี้ ดังนั้นฉันจึงอยากจะแสดงให้คุณเห็นปัญหาบางอย่างที่พวกเขาแก้ไข

สิ่งเหล่านี้ล้วนสะท้อนถึงยุคแรกเริ่มของปัญญาประดิษฐ์ ในช่วงทศวรรษที่ 50 และ 60 ปัญญาประดิษฐ์มุ่งเน้นไปที่การประมวลผลโครงสร้างภาษาเป็นหลัก เทคโนโลยีหลายอย่างที่ใช้ในคอมไพเลอร์สมัยใหม่ถูกคิดค้นขึ้นเพื่อแยกวิเคราะห์ภาษาของมนุษย์

เราทุกคนเคยไปที่นั่น

สมมติว่าเรากำลังสร้างเกมแพลตฟอร์มแบบเลื่อนด้านข้างขนาดเล็ก หน้าที่ของเราคือสร้างโมเดลนางเอกที่จะเป็นอวตารของผู้เล่นในโลกของเกม ซึ่งหมายความว่าจะต้องตอบสนองต่อการป้อนข้อมูลของผู้ใช้ กด B แล้วเธอจะกระโดด ค่อนข้างง่าย:

เป็นโมฆะ Heroine::handleInput (อินพุตอินพุต) ( ถ้า (อินพุต == PRESS_B) ( yVelocity_ = JUMP_VELOCITY; setGraphics(IMAGE_JUMP); ) )

สังเกตเห็นข้อผิดพลาดหรือไม่?

ไม่มีรหัสที่นี่เพื่อป้องกัน "การกระโดดขึ้นไปในอากาศ"; กด B ต่อไปในขณะที่เธออยู่ในอากาศ แล้วเธอจะบินขึ้นครั้งแล้วครั้งเล่า วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือการเพิ่มแฟล็กบูลีน isJumping_ ให้กับ Heroine ซึ่งจะคอยติดตามว่านางเอกกระโดดเมื่อใด:

ถือเป็นโมฆะ Heroine::handleInput(อินพุตอินพุต) ( if (input == PRESS_B) ( if (!isJumping_) ( isJumping_ = true ; // Jump... ) ) )

นอกจากนี้เรายังต้องการโค้ดที่จะตั้งค่า isJumping_ กลับเป็น false เมื่อนางเอกแตะพื้นอีกครั้ง เพื่อความง่าย ฉันไม่ใส่รหัสนี้

เป็นโมฆะ Heroine::handleInput (อินพุตอินพุต) ( ถ้า (อินพุต == PRESS_B) ( //โดดเลยถ้ายังไม่ได้...) else if (input == PRESS_DOWN) ( if (!isJumping_) ( setGraphics(IMAGE_DUCK); ) ) else if (input == RELEASE_DOWN) ( setGraphics(IMAGE_STAND); ) )

คุณสังเกตเห็นข้อผิดพลาดที่นี่หรือไม่?

ด้วยรหัสนี้ ผู้เล่นสามารถ:

  1. กดลงไปหมอบ
  2. กด B เพื่อกระโดดจากท่านั่ง
  3. ปล่อยลงขณะอยู่ในอากาศ

ขณะเดียวกันนางเอกจะเปลี่ยนไปใช้กราฟิคการยืนอยู่กลางอากาศ เราจะต้องเพิ่มธงอื่น ...

void Heroine::handleInput(อินพุตอินพุต) ( if (input == PRESS_B) ( if (!isJumping_ && !isDucking_) ( // Jump... ) ) else if (input == PRESS_DOWN) ( if (!isJumping_) ( isDucking_ = true ; setGraphics(IMAGE_DUCK); ) ) else if (input == RELEASE_DOWN) ( if (isDucking_) ( isDucking_ = false ; setGraphics(IMAGE_STAND); ) ) )

ตอนนี้มันจะเป็นการดีที่จะเพิ่มความสามารถให้นางเอกในการโจมตีด้วยการเข้าปะทะเมื่อผู้เล่นกดลงขณะอยู่ในอากาศ:

void Heroine::handleInput(อินพุตอินพุต) ( if (input == PRESS_B) ( if (!isJumping_ && !isDucking_) ( // Jump... ) ) else if (input == PRESS_DOWN) ( if (!isJumping_) ( isDucking_ = true ; setGraphics(IMAGE_DUCK); ) else ( isJumping_ = false ; setGraphics(IMAGE_DIVE); ) ) else if (input == RELEASE_DOWN) ( if (isDucking_) ( // Standing... ) ) )

มองหาจุดบกพร่องอีกครั้ง พบมัน?

เรามีการตรวจสอบเพื่อให้ไม่สามารถกระโดดขึ้นไปในอากาศได้ แต่จะไม่สามารถทำได้ในระหว่างการเข้าปะทะ เพิ่มอีกธง...

มีบางอย่างผิดปกติกับแนวทางนี้ ทุกครั้งที่เราสัมผัสโค้ด จะมีบางอย่างพัง เราจะต้องเพิ่มความเคลื่อนไหวอีกมาก เราไม่มีด้วยซ้ำ ที่เดินไม่ แต่ด้วยวิธีนี้ เราจะต้องเอาชนะข้อบกพร่องต่างๆ มากมาย

โปรแกรมเมอร์ที่เราทุกคนใฝ่ฝันและผู้สร้างโค้ดที่ยอดเยี่ยมนั้นไม่ใช่ซุปเปอร์แมนเลย พวกเขาเพียงแค่พัฒนาสัญชาตญาณสำหรับโค้ดที่ขู่ว่าจะทำให้เกิดข้อผิดพลาดและพยายามหลีกเลี่ยงทุกครั้งที่เป็นไปได้

การแตกสาขาและสถานะการเปลี่ยนแปลงที่ซับซ้อนนั้นเป็นประเภทของโค้ดที่คุณควรหลีกเลี่ยง

เครื่องจักรของรัฐที่มีขอบเขตจำกัดคือความรอดของเรา

ด้วยความหงุดหงิด คุณจึงเอาทุกอย่างออกจากโต๊ะ ยกเว้นดินสอและกระดาษ และเริ่มวาดผังงาน เราวาดรูปสี่เหลี่ยมผืนผ้าสำหรับแต่ละการกระทำที่นางเอกสามารถทำได้: ยืน กระโดด หมอบ และกลิ้ง เพื่อให้สามารถตอบสนองต่อการกดปุ่มในรัฐใดๆ เราจึงวาดลูกศรระหว่างสี่เหลี่ยมเหล่านี้ ติดป้ายกำกับปุ่มไว้ด้านบน และเชื่อมต่อรัฐต่างๆ เข้าด้วยกัน

ยินดีด้วย คุณเพิ่งสร้างขึ้น เครื่องของรัฐ (เครื่องสถานะจำกัด). พวกเขามาจากสาขาวิทยาการคอมพิวเตอร์ที่เรียกว่า ทฤษฎีออโตมาตะ (ทฤษฎีออโตมาตะ) ซึ่งมีโครงสร้างในตระกูลรวมถึงเครื่องจักรทัวริงอันโด่งดังด้วย FSM เป็นสมาชิกที่ง่ายที่สุดของครอบครัวนี้

บรรทัดล่างคือ:

    เรามีชุดคงที่ รัฐซึ่งอาจมีปืนกลอยู่ด้วยในตัวอย่างของเรา สิ่งเหล่านี้คือการยืน กระโดด หมอบ และกลิ้ง

    เครื่องเข้าได้อย่างเดียวครับ หนึ่งรัฐในเวลาใดก็ตามนางเอกของเราไม่สามารถกระโดดและยืนพร้อมกันได้ จริงๆ แล้ว FSM ถูกใช้ตั้งแต่แรกเพื่อป้องกันสิ่งนี้

    ลำดับต่อมา ป้อนข้อมูลหรือ เหตุการณ์ต่างๆ,ส่งเข้าเครื่อง.ในตัวอย่างของเรา นี่คือการกดและปล่อยปุ่ม

    แต่ละรัฐก็มี ชุดการเปลี่ยนแปลงซึ่งแต่ละรายการเชื่อมโยงกับอินพุตและระบุสถานะเมื่ออินพุตของผู้ใช้เกิดขึ้น หากตรงกับสถานะปัจจุบัน เครื่องจะเปลี่ยนสถานะไปยังตำแหน่งที่ลูกศรชี้

    ตัวอย่างเช่น หากคุณกดลงขณะยืน มันจะเปลี่ยนเป็นสถานะนั่งยองๆ การกดลงขณะกระโดดจะเปลี่ยนสถานะที่จะรับมือ หากในสถานะปัจจุบันไม่มีการระบุการเปลี่ยนแปลงสำหรับการป้อนข้อมูล ก็ไม่มีอะไรเกิดขึ้น

ในรูปแบบที่บริสุทธิ์ที่สุด นี่คือกล้วยทั้งหมด: สถานะ ข้อมูลเข้า และการเปลี่ยนภาพ คุณสามารถพรรณนาพวกมันได้ในรูปแบบของบล็อกไดอะแกรม น่าเสียดายที่คอมไพเลอร์จะไม่เข้าใจการเขียนลวก ๆ ดังกล่าว แล้วยังไงล่ะ ดำเนินการเครื่องสถานะจำกัด? The Gang of Four นำเสนอเวอร์ชันของตัวเอง แต่เราจะเริ่มต้นด้วยเวอร์ชันที่เรียบง่ายกว่านั้น

การเปรียบเทียบ FSM ที่ฉันชอบคือการค้นหาข้อความเก่า Zork คุณมีโลกที่ประกอบด้วยห้องที่เชื่อมต่อกันด้วยทางเดิน และคุณสามารถสำรวจพวกมันได้ด้วยการป้อนคำสั่งเช่น "ไปทางเหนือ"

แผนที่ดังกล่าวสอดคล้องกับคำจำกัดความของเครื่องจักรสถานะจำกัดอย่างสมบูรณ์ ห้องที่คุณอยู่คือสถานะปัจจุบัน ทุกการออกจากห้องคือการเปลี่ยนแปลง คำสั่งการนำทาง - อินพุต

การแจงนับและสวิตช์

ปัญหาอย่างหนึ่งของคลาส Heroine เก่าของเราก็คือ มันอนุญาตให้รวมคีย์บูลีนที่ไม่ถูกต้อง: isJumping_ และ isDucking_ ซึ่งไม่สามารถเป็นจริงได้ในเวลาเดียวกัน และถ้าคุณมีแฟล็กบูลีนหลายแฟล็ก ซึ่งมีเพียงแฟล็กเดียวที่สามารถเป็นจริงได้ จะดีกว่าไหมถ้าแทนที่แฟล็กทั้งหมดด้วย enum

ในกรณีของเรา การใช้ enum ทำให้เราสามารถอธิบายสถานะทั้งหมดของ FSM ได้อย่างสมบูรณ์ในลักษณะนี้:

สถานะ enum ( STATE_STANDING, STATE_JUMPING, STATE_DUCKING, STATE_DIVING );

แทนที่จะเป็นกลุ่มธง Heroine จะมีช่อง state_ เพียงช่องเดียวเท่านั้น เราจะต้องเปลี่ยนลำดับการแตกแขนงด้วย ในตัวอย่างโค้ดก่อนหน้านี้ เราแยกสาขาก่อนขึ้นอยู่กับอินพุต และจากนั้นจึงแยกตามสถานะ ในการทำเช่นนั้น เราได้จัดกลุ่มโค้ดตามการกดปุ่ม แต่เบลอโค้ดที่เกี่ยวข้องกับสถานะต่างๆ ตอนนี้เราจะทำสิ่งที่ตรงกันข้ามและเปลี่ยนอินพุตตามสถานะ นี่คือสิ่งที่เราได้รับ:

เป็นโมฆะ Heroine :: handleInput (อินพุตอินพุต) ( สวิตช์ (state_) ( กรณี STATE_STANDING: ถ้า (อินพุต == PRESS_B) ( state_ = STATE_JUMPING; yVelocity_ = JUMP_VELOCITY; setGraphics (IMAGE_JUMP); ) else if (อินพุต == PRESS_DOWN) ( state_ = STATE_DUCKING; setGraphics(IMAGE_DUCK); ) break ; case STATE_JUMPING: if (input == PRESS_DOWN) ( state_ = STATE_DIVING; setGraphics(IMAGE_DIVE); ) break ; case STATE_DUCKING: if (input == RELEASE_DOWN) ( state_ = STATE_STANDING; setGraphics (IMAGE_STAND); ) แตก ; ) )

มันดูค่อนข้างเล็กน้อย แต่ถึงกระนั้นรหัสนี้ก็ดีกว่ารหัสก่อนหน้ามากอยู่แล้ว เรายังคงมีการแยกย่อยแบบมีเงื่อนไขอยู่บ้าง แต่เราได้ลดความซับซ้อนของสถานะที่ไม่แน่นอนให้เป็นฟิลด์เดียว รหัสทั้งหมดที่จัดการสถานะเดียวจะถูกรวบรวมไว้ในที่เดียว นี่เป็นวิธีที่ง่ายที่สุดในการใช้เครื่องสถานะจำกัด และบางครั้งก็เพียงพอแล้ว

ตอนนี้นางเอกคงเข้าไม่ได้แล้ว ไม่แน่นอนเงื่อนไข. เมื่อใช้แฟล็กบูลีน อาจมีการรวมกันบางอย่างได้ แต่ก็ไม่สมเหตุสมผล เมื่อใช้แจงนับค่าทั้งหมดถูกต้อง

น่าเสียดายที่ปัญหาของคุณอาจเติบโตเร็วกว่าวิธีแก้ปัญหานี้ สมมติว่าเราต้องการเพิ่มการโจมตีพิเศษให้กับนางเอกของเรา โดยที่นางเอกต้องนั่งลงเพื่อชาร์จพลังแล้วปลดปล่อยพลังงานที่สะสมไว้ และในขณะที่เรากำลังนั่งเราต้องคอยสังเกตเวลาในการชาร์จด้วย

เพิ่มฟิลด์ chargeTime_ ให้กับ Heroine เพื่อจัดเก็บเวลาในการชาร์จ สมมติว่าเรามีเมธอด update() ที่ถูกเรียกในทุกเฟรมอยู่แล้ว มาเพิ่มโค้ดต่อไปนี้ลงไป:

เป็นโมฆะนางเอก::update() ( if (state_ == STATE_DUCKING) ( chargeTime_++; if (chargeTime_ > MAX_CHARGE) ( superBomb(); ) ) )

หากคุณทายรูปแบบ Update Method ได้รางวัล!

ทุกครั้งที่เราย่อตัวอีกครั้ง เราต้องรีเซ็ตตัวจับเวลานี้ ในการทำเช่นนี้เราจำเป็นต้องเปลี่ยน handleInput() :

เป็นโมฆะ Heroine :: handleInput (อินพุตอินพุต) ( สวิตช์ (state_) ( กรณี STATE_STANDING: ถ้า (อินพุต == PRESS_DOWN) ( state_ = STATE_DUCKING; chargeTime_ = 0 ; setGraphics (IMAGE_DUCK); ) // ประมวลผลอินพุตที่เหลือ...หยุดพัก ; // รัฐอื่นๆ... } }

ในท้ายที่สุด เพื่อเพิ่มการโจมตีด้วยประจุนี้ เราต้องเปลี่ยนสองวิธีและเพิ่มฟิลด์ chargeTime_ ให้กับ Heroine แม้ว่าจะใช้ในสถานะหมอบเท่านั้นก็ตาม ฉันต้องการให้รหัสและข้อมูลทั้งหมดนี้รวมอยู่ในที่เดียว แก๊งค์สี่สามารถช่วยเราได้ในเรื่องนี้

สถานะเทมเพลต

สำหรับผู้ที่เชี่ยวชาญกระบวนทัศน์เชิงวัตถุ ทุกสาขาที่มีเงื่อนไขคือโอกาสในการใช้การจัดส่งแบบไดนามิก (หรืออีกนัยหนึ่งคือการเรียกใช้เมธอดเสมือนใน C++) ฉันคิดว่าเราต้องเจาะลึกเข้าไปในโพรงกระต่ายนี้ให้ลึกลงไปอีก บางครั้งถ้าคือทั้งหมดที่เราต้องการ

มีพื้นฐานทางประวัติศาสตร์สำหรับเรื่องนี้ บรรดาอัครสาวกรุ่นเก่าหลายท่านที่มีกระบวนทัศน์เชิงวัตถุ เช่น แก๊งสี่ตนและพวกตน รูปแบบการเขียนโปรแกรมและมาร์ติน ฟูลเลอร์กับเขาด้วย การปรับโครงสร้างใหม่มาจากสมอลทอล์ค และ ifThen เป็นเพียงวิธีการที่คุณใช้ในการประมวลผลเงื่อนไขและมีการใช้งานที่แตกต่างกันสำหรับออบเจ็กต์จริงและเท็จ

ในตัวอย่างของเรา เราได้มาถึงจุดวิกฤติแล้วซึ่งเราควรให้ความสนใจกับสิ่งที่เป็นเชิงวัตถุ สิ่งนี้นำเราไปสู่รูปแบบของรัฐ หากต้องการอ้างอิง Gang of Four:

อนุญาตให้วัตถุเปลี่ยนพฤติกรรมตามการเปลี่ยนแปลงสถานะภายใน ในกรณีนี้วัตถุจะทำงานเหมือนคลาสอื่น

มันไม่ชัดเจนมาก ท้ายที่สุดก็เปลี่ยนรับมือกับเรื่องนี้ด้วย ตามตัวอย่างของเรากับนางเอก เทมเพลตจะมีลักษณะดังนี้:

อินเทอร์เฟซสถานะ

ขั้นแรก เรามากำหนดอินเทอร์เฟซสำหรับสถานะกันก่อน พฤติกรรมที่ขึ้นอยู่กับรัฐแต่ละส่วน—เช่น ทุกสิ่งที่เราใช้งานก่อนหน้านี้โดยใช้สวิตช์จะกลายเป็นวิธีการเสมือนของอินเทอร์เฟซนี้ ในกรณีของเรา สิ่งเหล่านี้คือ handleInput() และ update()

คลาส HeroineState ( สาธารณะ : เสมือน ~HeroineState() () หมายเลขอ้างอิงโมฆะเสมือน{} {} };

ชั้นเรียนสำหรับแต่ละรัฐ

สำหรับแต่ละรัฐ เรากำหนดคลาสที่ใช้อินเทอร์เฟซ วิธีการของเขากำหนดพฤติกรรมของนางเอกในสภาวะนี้ กล่าวอีกนัยหนึ่ง เราใช้ตัวเลือกทั้งหมดจากสวิตช์ในตัวอย่างก่อนหน้าและเปลี่ยนให้เป็นคลาสสถานะ ตัวอย่างเช่น:

คลาส DuckingState: สาธารณะ HeroineState ( สาธารณะ : DuckingState() : chargeTime_(0 ) () หมายเลขอ้างอิงโมฆะเสมือน (นางเอกและนางเอก, ข้อมูลเข้า)( ถ้า (อินพุต == RELEASE_DOWN) ( // การเปลี่ยนไปสู่สถานะยืน... heroine.setGraphics (IMAGE_STAND); ) ) การอัปเดตโมฆะเสมือน (นางเอกและนางเอก)( chargeTime__++; if (chargeTime_ > MAX_CHARGE) ( heroine.superBomb(); ) ) ส่วนตัว : int chargeTime_; );

โปรดทราบว่าเราได้ย้าย chargeTime_ จากคลาสของนางเอกไปที่คลาส DuckingState และนี่เป็นสิ่งที่ดีมาก เพราะข้อมูลชิ้นนี้มีความหมายเฉพาะในสถานะนี้เท่านั้น และแบบจำลองข้อมูลของเราก็ระบุสิ่งนี้อย่างชัดเจน

การมอบหมายให้รัฐ

นางเอกชั้น ( public : โมฆะเสมือน handleInput (อินพุตอินพุต)( state_->handleInput(*this อินพุต); ) การอัปเดตเป็นโมฆะเสมือน ()( state_->update(*this ); ) // วิธีอื่นๆ...ส่วนตัว : HeroineState* state_; );

หากต้องการ "เปลี่ยนสถานะ" เราเพียงแค่ต้องทำให้ state_ ชี้ไปที่วัตถุ HeroineState อื่น นี่คือสิ่งที่รูปแบบของรัฐประกอบด้วยจริงๆ

ดูค่อนข้างคล้ายกับเทมเพลต Strategy และ Type Object ของ GoF ในทั้งสามเรามีวัตถุหลักที่มอบหมายให้กับทาส ความแตกต่างก็คือ วัตถุประสงค์.

  • วัตถุประสงค์ของยุทธศาสตร์คือ การเชื่อมต่อลดลง(แยกส่วน) ระหว่างคลาสหลักและพฤติกรรมของมัน
  • วัตถุประสงค์ของวัตถุประเภทคือการสร้างวัตถุจำนวนหนึ่งที่ทำงานเหมือนกันโดยการแบ่งปันวัตถุประเภททั่วไประหว่างกัน
  • วัตถุประสงค์ของรัฐคือการเปลี่ยนแปลงพฤติกรรมของวัตถุหลักโดยการเปลี่ยนวัตถุที่รัฐมอบหมายให้

วัตถุของรัฐเหล่านี้อยู่ที่ไหน?

มีบางอย่างที่ฉันไม่ได้บอกคุณ ในการเปลี่ยนสถานะ เราจำเป็นต้องกำหนดค่า state_ ใหม่โดยชี้ไปที่สถานะใหม่ แต่วัตถุนี้มาจากไหน ในตัวอย่างของเรา ไม่มีอะไรต้องคิด: ค่าแจงนับเป็นเพียงค่าพื้นฐานเหมือนกับตัวเลข แต่ตอนนี้รัฐของเราถูกแสดงด้วยคลาส ซึ่งหมายความว่าเราต้องการตัวชี้ไปยังอินสแตนซ์จริง มีสองคำตอบที่พบบ่อยที่สุด:

รัฐคงที่

ถ้าวัตถุสถานะไม่มีฟิลด์อื่น สิ่งเดียวที่มันเก็บไว้คือตัวชี้ไปยังตารางเสมือนภายในของวิธีการเพื่อให้สามารถเรียกใช้วิธีการเหล่านั้นได้ ในกรณีนี้ ไม่จำเป็นต้องมีอินสแตนซ์ของคลาสมากกว่าหนึ่งอินสแตนซ์ แต่ละอินสแตนซ์จะยังคงเหมือนเดิม

หากรัฐของคุณไม่มีฟิลด์และมีวิธีการเสมือนเพียงวิธีเดียว คุณสามารถทำให้รูปแบบง่ายขึ้นได้อีก เราจะแทนที่แต่ละ ระดับสถานะ การทำงาน state - ฟังก์ชันระดับบนสุดปกติ และตามสนาม สถานะ_ในคลาสหลักของเราจะกลายเป็นตัวชี้ฟังก์ชันอย่างง่าย

ค่อนข้างเป็นไปได้ที่จะผ่านไปด้วยเพียงหนึ่งเดียว คงที่สำเนา. แม้ว่าคุณจะมี FSM จำนวนมากทั้งหมดอยู่ในสถานะเดียวกันในเวลาเดียวกัน แต่ FSM ทั้งหมดก็สามารถชี้ไปที่อินสแตนซ์แบบคงที่เดียวกันได้ เนื่องจากไม่มีสถานะเฉพาะเจาะจงของเครื่อง

ตำแหน่งที่คุณวางอินสแตนซ์แบบคงที่นั้นขึ้นอยู่กับคุณ หาสถานที่ที่เหมาะสม มาวางอินสแตนซ์ของเราไว้ในคลาสพื้นฐาน ไม่มีเหตุผล.

คลาส HeroineState (สาธารณะ: การยืน StandingState แบบคงที่; การเป็ด DuckingState แบบคงที่; การกระโดด JumpingState แบบคงที่; การดำน้ำแบบคงที่ DivingState; //โค้ดที่เหลือ... };

ฟิลด์คงที่แต่ละฟิลด์เป็นตัวอย่างของสถานะที่เกมใช้ เพื่อให้นางเอกกระโดด สถานะยืนจะทำดังนี้:

ถ้า (อินพุต == PRESS_B) ( heroine.state_ = &HeroineState::jumping; heroine.setGraphics(IMAGE_JUMP); )

อินสแตนซ์ของรัฐ

บางครั้งตัวเลือกก่อนหน้าก็ไม่ได้ปิดลง สถานะคงที่ไม่เหมาะกับสถานะหมอบคลาน มีช่อง chargeTime_ และช่องเฉพาะสำหรับนางเอกที่จะหมอบอยู่ สิ่งนี้จะได้ผลดียิ่งขึ้นในกรณีของเรา เนื่องจากเรามีนางเอกเพียงคนเดียว แต่ถ้าเราต้องการเพิ่ม Co-op สำหรับผู้เล่นสองคน เราก็จะประสบปัญหาใหญ่

ในกรณีนี้ เราควรสร้างวัตถุสถานะเมื่อเราย้ายเข้าไป สิ่งนี้จะทำให้แต่ละ FSM มีอินสแตนซ์สถานะของตัวเอง แน่นอนว่าถ้าเราจัดสรรหน่วยความจำให้ ใหม่สภาพนี้หมายความว่าเราควร ปล่อยความทรงจำที่ถูกครอบครองในปัจจุบัน เราต้องระวังเพราะโค้ดที่ทำให้เกิดการเปลี่ยนแปลงอยู่ในสถานะปัจจุบันของเมธอด เราไม่ต้องการเอาสิ่งนี้ออกจากข้างใต้ตัวเรา

แต่เราจะให้ handleInput() บน HeroineState เลือกที่จะส่งคืนสถานะใหม่แทน เมื่อสิ่งนี้เกิดขึ้น นางเอกจะลบสถานะเก่าและแทนที่ด้วยสถานะใหม่ เช่นนี้

เป็นโมฆะ Heroine::handleInput(อินพุตอินพุต) ( HeroineState* state = state_->handleInput(*this , input); if (state != NULL ) ( ลบ state_; state_ = state; ) )

วิธีนี้เราจะไม่ลบสถานะก่อนหน้าจนกว่าเราจะคืนจากวิธีการของเรา ตอนนี้ สถานะยืนสามารถเปลี่ยนเป็นสถานะดำน้ำได้โดยการสร้างอินสแตนซ์ใหม่:

HeroineState* StandingState::handleInput(Heroine& heroine, Input input) ( if (input == PRESS_DOWN) ( // Other code... return new DuckingState(); ) // คงอยู่ในสถานะนี้ return NULL ; )

เมื่อทำได้ ฉันชอบใช้สถานะคงที่เนื่องจากไม่ใช้หน่วยความจำและวงจรของ CPU โดยการจัดสรรอ็อบเจ็กต์ทุกครั้งที่สถานะเปลี่ยนแปลง สำหรับเงื่อนไขที่ไม่ใช่แค่เพียง สถานะ- นี่คือสิ่งที่คุณต้องการ

แน่นอน เมื่อคุณจัดสรรหน่วยความจำสำหรับสถานะแบบไดนามิก คุณควรคำนึงถึงการกระจายตัวของหน่วยความจำที่เป็นไปได้ เทมเพลต Object Pool สามารถช่วยได้

ขั้นตอนการเข้าสู่ระบบและออกจากระบบ

รูปแบบสถานะได้รับการออกแบบมาเพื่อสรุปพฤติกรรมทั้งหมดและข้อมูลที่เกี่ยวข้องภายในคลาสเดียว เราทำผลงานได้ค่อนข้างดี แต่ยังมีรายละเอียดที่ไม่ชัดเจนอยู่บ้าง

เมื่อนางเอกเปลี่ยนสถานะ เราก็เปลี่ยนสไปรท์ของเธอด้วย ตอนนี้รหัสนี้เป็นของรัฐด้วย ใครเธอเปลี่ยน เมื่อรัฐผ่านจากการดำน้ำไปสู่การยืน การดำน้ำจะสร้างภาพลักษณ์:

HeroineState* DuckingState::handleInput(Heroine& heroine, Input input) ( if (input == RELEASE_DOWN) ( heroine.setGraphics(IMAGE_STAND); return new StandingState(); ) // Other code... )

สิ่งที่เราต้องการจริงๆ คือแต่ละรัฐต้องควบคุมกราฟิกของตัวเอง เราสามารถบรรลุเป้าหมายนี้ได้โดยการเพิ่มเข้าไปในรัฐ การดำเนินการป้อนข้อมูล (การดำเนินการเข้า):

class StandingState: สาธารณะ HeroineState ( สาธารณะ : เข้าสู่โมฆะเสมือน (นางเอก & นางเอก)( heroine.setGraphics(IMAGE_STAND); ) // รหัสอื่น ๆ... );

เมื่อกลับมาที่ Heroine เราจะแก้ไขโค้ดเพื่อให้แน่ใจว่าการเปลี่ยนแปลงสถานะจะมาพร้อมกับการเรียกฟังก์ชันการดำเนินการป้อนข้อมูลของสถานะใหม่:

เป็นโมฆะ Heroine::handleInput(อินพุตอินพุต) ( HeroineState* state = state_->handleInput(*this , input); if (state != NULL ) ( ลบ state_; state_ = state; // เรียกการดำเนินการอินพุตของสถานะใหม่ state_->enter(*นี้ ); ) )

สิ่งนี้จะทำให้รหัส DuckingState ง่ายขึ้น:

HeroineState* DuckingState::handleInput(Heroine& heroine, Input input) ( if (input == RELEASE_DOWN) ( return new StandingState(); ) // รหัสอื่น ๆ... )

ทั้งหมดนี้คือการสลับไปที่การยืน และสถานะการยืนจะดูแลกราฟิก ตอนนี้รัฐของเราถูกห่อหุ้มไว้อย่างแท้จริง คุณสมบัติที่ดีอีกประการหนึ่งของการดำเนินการอินพุตดังกล่าวก็คือ มันถูกทริกเกอร์เมื่อเข้าสู่สถานะ โดยไม่คำนึงถึงสถานะใน ที่เราอยู่ที่นั่น.

กราฟสถานะในชีวิตจริงส่วนใหญ่มีการเปลี่ยนสถานะเป็นสถานะเดียวกันหลายครั้ง เช่น นางเอกของเราสามารถยิงอาวุธขณะยืน นั่ง หรือกระโดดได้ ซึ่งหมายความว่าเราอาจมีการทำซ้ำโค้ดไม่ว่าจะเกิดขึ้นที่ไหนก็ตาม การดำเนินการป้อนข้อมูลช่วยให้คุณสามารถรวบรวมได้ในที่เดียว

คุณสามารถทำได้โดยการเปรียบเทียบ การกระทำเอาท์พุท (ออกจากการกระทำ). นี่ก็จะเป็นเพียงวิธีการที่เราจะเรียกรัฐมาก่อน ออกไปและเปลี่ยนเป็นสถานะใหม่

และเราประสบความสำเร็จอะไร?

ฉันใช้เวลามากมายในการขาย FSM ให้คุณ และตอนนี้ฉันกำลังจะดึงพรมออกจากใต้ตัวคุณ ทุกสิ่งที่ฉันพูดมาทั้งหมดเป็นความจริงและเป็นวิธีการแก้ปัญหาที่ยอดเยี่ยม แต่มันก็บังเอิญว่าข้อดีที่สำคัญที่สุดของเครื่องจักรที่มีสถานะจำกัดก็คือข้อเสียที่ใหญ่ที่สุดเช่นกัน

เครื่องสถานะช่วยให้คุณแก้โค้ดของคุณให้หายยุ่งได้อย่างจริงจังโดยการจัดระเบียบให้เป็นโครงสร้างที่เข้มงวดมาก ทั้งหมดที่เรามีคือชุดสถานะคงที่ สถานะปัจจุบันเดียว และการเปลี่ยนแบบฮาร์ดโค้ด

หุ่นยนต์ทัวริงที่มีขอบเขตจำกัดยังไม่สมบูรณ์ ทฤษฎีออโตมาตาอธิบายความสมบูรณ์ผ่านแบบจำลองเชิงนามธรรมหลายชุด ซึ่งแต่ละแบบจำลองมีความซับซ้อนมากกว่าครั้งก่อน เครื่องจักรทัวริงเป็นหนึ่งในเครื่องที่แสดงออกได้ดีที่สุด

"ทัวริงเสร็จสมบูรณ์" หมายถึงระบบ (โดยปกติคือภาษาการเขียนโปรแกรม) ที่แสดงออกเพียงพอที่จะนำเครื่องจักรทัวริงไปใช้ ซึ่งหมายความว่าภาษาทัวริงที่สมบูรณ์ทั้งหมดมีการแสดงออกที่เท่าเทียมกันโดยประมาณ FSM ไม่แสดงออกเพียงพอที่จะเข้าสู่สโมสรแห่งนี้

หากคุณพยายามใช้เครื่องสถานะสำหรับสิ่งที่ซับซ้อนมากขึ้น เช่น เกม AI คุณจะพบกับข้อจำกัดของโมเดลนี้ทันที โชคดีที่รุ่นก่อนๆ ของเราได้เรียนรู้ที่จะหลีกเลี่ยงอุปสรรคบางประการ ผมจะจบบทนี้ด้วยตัวอย่างบางส่วน

เครื่องสถานะการแข่งขัน

เราตัดสินใจเพิ่มความสามารถให้นางเอกของเราในการพกพาอาวุธ แม้ว่าตอนนี้เธอจะติดอาวุธแล้ว แต่เธอก็ยังสามารถทำทุกอย่างที่เคยทำได้ เช่น วิ่ง กระโดด หมอบ ฯลฯ แต่ตอนนี้ ขณะที่ทำทั้งหมดนี้ เธอยังสามารถยิงอาวุธได้อีกด้วย

หากเราต้องการปรับพฤติกรรมนี้ให้เข้ากับกรอบงาน FSM เราจะต้องเพิ่มจำนวนสถานะเป็นสองเท่า สำหรับแต่ละรัฐ เราจะต้องสร้างรัฐที่เหมือนกันอีกแห่ง แต่สำหรับนางเอกที่ถืออาวุธ: ยืน ยืนถืออาวุธ กระโดด กระโดดด้วยอาวุธ.... คุณเข้าใจแนวคิดนี้แล้ว

หากคุณเพิ่มอาวุธอีกสองสามชิ้น จำนวนสถานะจะเพิ่มขึ้นแบบรวมกัน และนี่ไม่ใช่แค่หลายรัฐเท่านั้น แต่ยังมีการทำซ้ำหลาย ๆ ครั้งด้วย: รัฐติดอาวุธและไม่ติดอาวุธเกือบจะเหมือนกันหมด ยกเว้นในส่วนของรหัสที่รับผิดชอบในการยิง

ปัญหาตรงนี้ก็คือเราสับสนสองส่วนของรัฐ-นั่นเอง ทำแล้วไง ถืออยู่ในมือ- ในเครื่องเดียว เพื่อจำลองชุดค่าผสมที่เป็นไปได้ทั้งหมด เราจำเป็นต้องสร้างสถานะสำหรับชุดค่าผสมแต่ละรายการ คู่รัก. วิธีแก้ปัญหานั้นชัดเจน: คุณต้องสร้างเครื่องสถานะแยกกันสองเครื่อง

ถ้าเราอยากจะสามัคคีกัน nสถานะของการกระทำและ สถานะของสิ่งที่เราถืออยู่ในมือของเราให้เป็นเครื่องจักรสถานะอันจำกัด - เราต้องการ ไม่มี×มรัฐ ถ้าเรามีปืนกลสองกระบอก เราก็จำเป็นต้องมี n+มรัฐ

เราจะปล่อยให้เครื่องสถานะเครื่องแรกของเราไม่มีการเปลี่ยนแปลง และนอกจากนั้นเราจะสร้างเครื่องขึ้นมาอีกเครื่องเพื่อบรรยายว่านางเอกถืออะไรอยู่ ตอนนี้ Heroine จะมีการอ้างอิง "สถานะ" สองรายการ หนึ่งรายการสำหรับแต่ละเครื่อง

นางเอกชั้น ( //โค้ดที่เหลือ...ส่วนตัว : HeroineState* state_; HeroineStat* อุปกรณ์_; );

เพื่อเป็นตัวอย่าง เราใช้รูปแบบสถานะเต็มรูปแบบสำหรับเครื่องสถานะที่สอง แม้ว่าในทางปฏิบัติแล้ว ธงบูลีนแบบธรรมดาก็เพียงพอแล้วในกรณีนี้

เมื่อนางเอกมอบหมายอินพุตให้กับรัฐ มันจะผ่านการแปลไปยังเครื่องของรัฐทั้งสองเครื่อง:

เป็นโมฆะ Heroine::handleInput(อินพุตอินพุต) ( state_->handleInput(*this , input); equipment_->handleInput(*this , input); )

ระบบที่ซับซ้อนมากขึ้นอาจรวมถึงเครื่องที่มีสถานะจำกัดซึ่งสามารถดูดซับส่วนหนึ่งของอินพุตเพื่อให้เครื่องอื่นไม่รับอีกต่อไป ซึ่งจะช่วยให้เราป้องกันสถานการณ์ที่เครื่องหลายเครื่องตอบสนองต่ออินพุตเดียวกันได้

เครื่องสถานะแต่ละเครื่องสามารถตอบสนองต่ออินพุต สร้างพฤติกรรม และเปลี่ยนสถานะได้โดยอิสระจากเครื่องสถานะอื่นๆ และเมื่อทั้งสองรัฐแทบไม่มีความเกี่ยวข้องกันเลย มันก็ใช้งานได้ดี

ในทางปฏิบัติ คุณอาจเผชิญกับสถานการณ์ที่รัฐมีปฏิสัมพันธ์กัน ตัวอย่างเช่น เธอไม่สามารถยิงขณะกระโดดได้ หรือทำการโจมตีแบบสไลด์เมื่อมีอาวุธ เพื่อให้แน่ใจว่าพฤติกรรมนี้และการประสานงานของออโตมาตะในโค้ด คุณจะต้องกลับไปใช้การตรวจสอบแบบ bruteforce แบบเดิมผ่าน if อื่นเครื่องสถานะจำกัด ไม่ใช่วิธีแก้ปัญหาที่หรูหราที่สุด แต่อย่างน้อยก็ใช้งานได้

เครื่องสถานะแบบลำดับชั้น

หลังจากฟื้นฟูพฤติกรรมของนางเอกแล้ว เธอคงจะมีสภาวะที่คล้ายกันมากมาย เช่น การยืน เดิน วิ่ง และเลื่อนลงเนินไม่สามารถเกิดขึ้นได้ ในรัฐเหล่านี้ การกด B ทำให้เธอกระโดด และการกดลงทำให้เธอหมอบลง

ในการใช้งานเครื่องสถานะที่ง่ายที่สุด เราได้ทำซ้ำรหัสนี้สำหรับทุกรัฐ แต่แน่นอนว่ามันจะดีกว่ามากถ้าเราต้องเขียนโค้ดเพียงครั้งเดียวแล้วเราก็สามารถนำมันกลับมาใช้ใหม่ได้ทุกรัฐ

หากนี่เป็นเพียงโค้ดเชิงวัตถุและไม่ใช่เครื่องสถานะ เราสามารถใช้เทคนิคในการแยกโค้ดระหว่างสถานะที่เรียกว่าการสืบทอด คุณสามารถกำหนดคลาสสำหรับสถานะภาคพื้นดินที่จะรองรับการกระโดดและการหมอบ การยืน เดิน วิ่ง และกลิ้งนั้นสืบทอดมาและเพิ่มพฤติกรรมของตนเองเพิ่มเติม

การตัดสินใจครั้งนี้มีทั้งผลดีและผลเสีย การสืบทอดเป็นเครื่องมือที่ทรงพลังสำหรับการนำโค้ดกลับมาใช้ซ้ำ แต่ในขณะเดียวกันก็ให้การทำงานร่วมกันที่แข็งแกร่งมากระหว่างโค้ดสองชิ้น ค้อนนั้นหนักเกินกว่าจะตีอย่างไร้เหตุผล

ในรูปแบบนี้จะมีการเรียกโครงสร้างผลลัพธ์ เครื่องสถานะแบบลำดับชั้น(หรือ หุ่นยนต์แบบลำดับชั้น). และแต่ละเงื่อนไขก็สามารถมีได้ รัฐเหนือ(รัฐเองเรียกว่า รัฐย่อย). เมื่อมีเหตุการณ์เกิดขึ้นและรัฐย่อยไม่ดำเนินการ เหตุการณ์ดังกล่าวจะถูกส่งต่อไปยังรัฐซุปเปอร์สเตต กล่าวอีกนัยหนึ่ง ดูเหมือนว่าเป็นการแทนที่วิธีที่สืบทอดมา

ในความเป็นจริง หากเราใช้รูปแบบ State ดั้งเดิมเพื่อนำ FSM ไปใช้ เราก็สามารถใช้การสืบทอดคลาสเพื่อนำลำดับชั้นไปใช้ได้แล้ว มากำหนดคลาสพื้นฐานสำหรับซูเปอร์คลาสกัน:

คลาส OnGroundState: HeroineState สาธารณะ ( สาธารณะ : หมายเลขอ้างอิงโมฆะเสมือน (นางเอกและนางเอก, ข้อมูลเข้า)( ถ้า (input == PRESS_B) ( // Jump... ) else if (input == PRESS_DOWN) ( // Squat... ) ) );

และตอนนี้แต่ละคลาสย่อยจะสืบทอดมัน:

คลาส DuckingState: สาธารณะ OnGroundState ( สาธารณะ : หมายเลขอ้างอิงโมฆะเสมือน (นางเอกและนางเอก, ข้อมูลเข้า)( ถ้า (input == RELEASE_DOWN) ( // ลุกขึ้น... ) else ( // อินพุตไม่ได้รับการประมวลผล ดังนั้นเราจึงส่งต่อมันให้สูงกว่าในลำดับชั้น OnGroundState::handleInput (นางเอก, อินพุต); ) ) );

แน่นอนว่านี่ไม่ใช่วิธีเดียวที่จะใช้ลำดับชั้นได้ แต่ถ้าคุณไม่ใช้เทมเพลต Gang of Four State มันจะไม่ทำงาน แต่คุณสามารถสร้างโมเดลลำดับชั้นที่ชัดเจนของรัฐปัจจุบันและรัฐเหนือแทนได้ ซ้อนกันระบุสถานะแทนที่จะเป็นสถานะเดียวในคลาสหลัก

สถานะปัจจุบันจะอยู่ที่ด้านบนสุดของสแต็ก ด้านล่างคือสถานะขั้นสูง จากนั้นจึงเป็นสถานะขั้นสูงสำหรับ นี้รัฐเหนือ ฯลฯ และเมื่อคุณต้องการใช้พฤติกรรมเฉพาะของรัฐ คุณจะเริ่มต้นจากด้านบนของสแต็กและค่อยๆ ลงไปจนกว่ารัฐจะจัดการได้ (และหากไม่ดำเนินการคุณก็เพิกเฉยต่อมัน)

เครื่องอัตโนมัติพร้อมหน่วยความจำนิตยสาร

มีส่วนขยายทั่วไปอีกประการหนึ่งสำหรับเครื่องสถานะที่ใช้สเตตสแต็กด้วย เฉพาะที่นี่เท่านั้นที่สแต็กแสดงถึงแนวคิดที่แตกต่างไปจากเดิมอย่างสิ้นเชิงและใช้เพื่อแก้ไขปัญหาที่แตกต่างกัน

ปัญหาคือเครื่องของรัฐไม่มีแนวคิด เรื่องราว. คุณรู้ไหมว่าคุณอยู่ในรัฐอะไร? คุณคือแต่คุณไม่มีข้อมูลเกี่ยวกับสถานะที่คุณอยู่ คือ. ดังนั้นจึงไม่มีวิธีง่ายๆ ในการกลับไปสู่สถานะก่อนหน้า

นี่เป็นตัวอย่างง่ายๆ: ก่อนหน้านี้เราปล่อยให้นางเอกผู้กล้าหาญของเรายอมทนกับฟัน เมื่อเธอยิงอาวุธ เราจำเป็นต้องมีสถานะใหม่ในการเล่นแอนิเมชั่นการยิง วางกระสุน และเอฟเฟกต์ภาพประกอบ ในการทำเช่นนี้ เราได้สร้าง FiringState ใหม่และทำการเปลี่ยนจากทุกสถานะที่นางเอกสามารถยิงได้โดยการกดปุ่มยิง

เนื่องจากพฤติกรรมนี้ซ้ำกันระหว่างหลายสถานะ นี่คือจุดที่เครื่องสถานะแบบลำดับชั้นสามารถใช้เพื่อนำโค้ดกลับมาใช้ใหม่ได้

ปัญหาที่นี่คือคุณต้องเข้าใจว่าคุณต้องไปที่สถานะใด หลังจากการยิง นางเอกสามารถยิงทั้งคลิปในขณะที่เธอยืนนิ่ง วิ่ง กระโดด หรือหมอบลง เมื่อลำดับการถ่ายทำเสร็จสิ้น เธอจะต้องกลับสู่สภาวะที่เป็นอยู่ก่อนที่จะถ่ายทำ

ถ้าเราผูกพันกับ FSM ล้วนๆ เราจะลืมทันทีว่าเราอยู่ในสถานะใด เพื่อติดตามสิ่งนี้ เราจำเป็นต้องกำหนดสถานะที่เกือบจะเหมือนกันหลายประการ เช่น การยิงแบบยืน การยิงแบบวิ่ง การยิงแบบกระโดด ฯลฯ ดังนั้นเราจึงมีการเปลี่ยนแบบฮาร์ดโค้ดซึ่งจะไปสู่สถานะที่ถูกต้องเมื่อเสร็จสิ้น

สิ่งที่เราต้องการจริงๆ ก็คือความสามารถในการบันทึกสภาพที่เราเป็นก่อนการยิง และจดจำมันอีกครั้งหลังการยิง ทฤษฎีออโตมาตะสามารถช่วยเราได้อีกครั้ง โครงสร้างข้อมูลที่เกี่ยวข้องเรียกว่า Pushdown Automaton

ในหุ่นยนต์ที่มีขอบเขตจำกัด เรามีตัวชี้เพียงตัวเดียวไปยังสถานะ ในหุ่นยนต์ที่มีหน่วยความจำจัดเก็บจะมี ซ้อนกัน. ใน FSM การเปลี่ยนไปสู่สถานะใหม่จะแทนที่สถานะก่อนหน้า เครื่องที่มีหน่วยความจำนิตยสารช่วยให้คุณทำสิ่งนี้ได้ แต่เพิ่มการทำงานอีกสองอย่าง:

    คุณสามารถ สถานที่ (ดัน) สถานะใหม่ลงบนสแต็ก สถานะปัจจุบันจะอยู่ที่ด้านบนของสแต็กเสมอ ดังนั้นนี่คือการดำเนินการเพื่อย้ายไปยังสถานะใหม่ แต่ในขณะเดียวกัน สถานะเก่ายังคงอยู่ต่ำกว่าสถานะปัจจุบันบนสแต็กโดยตรง และไม่หายไปอย่างไร้ร่องรอย

    คุณสามารถ สารสกัด (โผล่) สถานะบนสุดจากสแต็ก รัฐหายไปและสิ่งที่อยู่ข้างใต้กลายเป็นปัจจุบัน

นั่นคือทั้งหมดที่เราต้องการสำหรับการยิง เราสร้าง สิ่งเดียวเท่านั้นสภาพการยิง เมื่อเรากดปุ่มไฟในขณะที่อยู่ในสถานะอื่นเรา สถานที่ (ดัน) สถานะการถ่ายภาพแบบกองซ้อน เมื่อภาพเคลื่อนไหวการถ่ายภาพสิ้นสุดลง เรา สารสกัด (โผล่) และเครื่องที่มีหน่วยความจำนิตยสารจะนำเรากลับสู่สถานะก่อนหน้าโดยอัตโนมัติ

พวกมันมีประโยชน์จริง ๆ แค่ไหน?

แม้จะมีการขยายเครื่องจักรของรัฐนี้ แต่ความสามารถของพวกเขาก็ยังค่อนข้างจำกัด ใน AI ทุกวันนี้ เทรนด์ที่แพร่หลายคือการใช้สิ่งต่าง ๆ เช่น ต้นไม้พฤติกรรม(แผนผังพฤติกรรม) และ ระบบการวางแผน(ระบบการวางแผน). และหากคุณสนใจในด้าน AI เป็นพิเศษ บททั้งหมดนี้น่าจะกระตุ้นความอยากอาหารของคุณ คุณจะต้องหันไปหาหนังสือเล่มอื่นเพื่อให้เขาพอใจ

นี่ไม่ได้หมายความว่าเครื่องจักรที่มีสถานะจำกัด เครื่องจักรที่มีหน่วยความจำนิตยสาร และระบบอื่นที่คล้ายคลึงกันนั้นไร้ประโยชน์โดยสิ้นเชิง สำหรับบางสิ่งสิ่งเหล่านี้เป็นเครื่องมือสร้างแบบจำลองที่ดี เครื่องสถานะมีประโยชน์เมื่อ:

  • คุณมีเอนทิตีที่พฤติกรรมเปลี่ยนแปลงไปขึ้นอยู่กับสถานะภายใน
  • เงื่อนไขนี้แบ่งออกเป็นตัวเลือกเฉพาะจำนวนค่อนข้างน้อยอย่างเคร่งครัด
  • เอนทิตีตอบสนองต่อชุดคำสั่งหรือเหตุการณ์อินพุตอย่างต่อเนื่อง

ในเกม โดยทั่วไปเครื่องสถานะจะใช้เพื่อสร้างโมเดล AI แต่ยังสามารถใช้เพื่อป้อนข้อมูลผู้ใช้ การนำทางเมนู การแยกวิเคราะห์ข้อความ โปรโตคอลเครือข่าย และพฤติกรรมอะซิงโครนัสอื่นๆ

"ลวดลายสถานะ"แหล่งที่มา.ru

สถานะเป็นรูปแบบพฤติกรรมของวัตถุที่ระบุการทำงานที่แตกต่างกัน ขึ้นอยู่กับสถานะภายในของวัตถุ เว็บไซต์ แหล่งที่มาของเว็บไซต์ ต้นฉบับ

เงื่อนไข งาน วัตถุประสงค์

อนุญาตให้วัตถุเปลี่ยนแปลงพฤติกรรมโดยขึ้นอยู่กับสถานะภายใน เนื่องจากพฤติกรรมสามารถเปลี่ยนแปลงได้อย่างสมบูรณ์โดยไม่มีข้อจำกัดใดๆ จากภายนอกจึงดูเหมือนว่าคลาสของวัตถุมีการเปลี่ยนแปลง

แรงจูงใจ

พิจารณาชั้นเรียน TCPConnectionซึ่งแสดงถึงการเชื่อมต่อเครือข่าย อ็อบเจ็กต์ของคลาสนี้สามารถอยู่ในสถานะใดสถานะหนึ่งได้: ที่จัดตั้งขึ้น(ติดตั้ง), การฟัง(การฟัง), ปิด(ปิด). เมื่อวัตถุ TCPConnectionรับคำขอจากออบเจ็กต์อื่น ๆ โดยจะตอบสนองแตกต่างกันไปขึ้นอยู่กับสถานะปัจจุบัน เช่น การตอบสนองต่อคำร้องขอ เปิด(เปิด) ขึ้นอยู่กับว่าการเชื่อมต่ออยู่ในสถานะหรือไม่ ปิดหรือ ที่จัดตั้งขึ้น. รูปแบบสถานะอธิบายถึงวิธีการของวัตถุ TCPConnectionสามารถประพฤติตนแตกต่างออกไปเมื่ออยู่ในสถานะที่ต่างกัน แหล่งที่มาของไซต์ ไซต์ดั้งเดิม

แนวคิดหลักของรูปแบบนี้คือการแนะนำคลาสนามธรรม TCPStateเพื่อแสดงสถานะการเชื่อมต่อที่แตกต่างกัน คลาสนี้ประกาศอินเทอร์เฟซที่ใช้ร่วมกันสำหรับทุกคลาสที่อธิบายผู้ปฏิบัติงานที่แตกต่างกัน ที่มาต้นฉบับ.ru

เงื่อนไข. ในคลาสย่อยเหล่านี้ TCPStateมีการนำพฤติกรรมเฉพาะของรัฐไปใช้ เช่น ในชั้นเรียน TCP ก่อตั้งแล้วและ TCPปิดแล้วมีการใช้พฤติกรรมเฉพาะของรัฐ ที่จัดตั้งขึ้นและ ปิดตามลำดับ เว็บไซต์ เว็บไซต์ แหล่งที่มาดั้งเดิม

ต้นฉบับ.ru

ระดับ TCPConnectionเก็บวัตถุสถานะ (อินสแตนซ์ของคลาสย่อย TCPState) แสดงถึงสถานะปัจจุบันของการเชื่อมต่อ และมอบหมายคำขอที่ขึ้นอยู่กับสถานะทั้งหมดให้กับออบเจ็กต์นี้ TCPConnectionใช้อินสแตนซ์ของคลาสย่อยของตัวเอง TCPStateค่อนข้างง่าย: การเรียกวิธีการของอินเทอร์เฟซเดียว TCPStateขึ้นอยู่กับคลาสย่อยเฉพาะที่จัดเก็บอยู่ในปัจจุบันเท่านั้น TCPState-a - ผลลัพธ์แตกต่างออกไป เช่น ในความเป็นจริง มีการดำเนินการเฉพาะกับสถานะการเชื่อมต่อนี้เท่านั้น ที่มาของ original.ru

และทุกครั้งที่สถานะการเชื่อมต่อเปลี่ยนแปลงTCPConnectionเปลี่ยนวัตถุสถานะ ตัวอย่างเช่น เมื่อการเชื่อมต่อที่สร้างไว้ถูกปิด TCPConnectionแทนที่อินสแตนซ์ของคลาส TCP ก่อตั้งแล้วสำเนา TCPปิดแล้ว. ไซต์ ไซต์ต้นทางดั้งเดิม

สัญญาณของการประยุกต์การใช้รูปแบบของรัฐ

ใช้รูปแบบสถานะในกรณีต่อไปนี้: source.ru
  1. เมื่อพฤติกรรมของวัตถุขึ้นอยู่กับสถานะและต้องเปลี่ยนแปลงขณะรันไทม์ ที่มา original.ru
  2. เมื่อรหัสการดำเนินการมีคำสั่งแบบมีเงื่อนไขที่ประกอบด้วยหลายสาขาซึ่งการเลือกสาขาขึ้นอยู่กับสถานะ โดยทั่วไปในกรณีนี้ รัฐจะแสดงด้วยค่าคงที่ที่ระบุ บ่อยครั้งที่โครงสร้างคำสั่งแบบมีเงื่อนไขเดียวกันถูกทำซ้ำในหลาย ๆ การดำเนินการ รูปแบบสถานะแนะนำให้วางแต่ละสาขาในคลาสที่แยกจากกัน สิ่งนี้ทำให้คุณสามารถปฏิบัติต่อสถานะของออบเจ็กต์ในฐานะออบเจ็กต์อิสระที่สามารถเปลี่ยนแปลงได้โดยอิสระจากสิ่งอื่น แหล่งที่มาของไซต์ ไซต์ดั้งเดิม

สารละลาย

เว็บไซต์ เว็บไซต์ แหล่งที่มาดั้งเดิม

แหล่งที่มา.ru

ผู้เข้าร่วมรูปแบบรัฐ

แหล่งที่มา.ru
  1. บริบท(TCPConnection) - บริบท
    กำหนดอินเทอร์เฟซเดียวสำหรับไคลเอ็นต์
    เก็บอินสแตนซ์ของคลาสย่อย รัฐคอนกรีตซึ่งกำหนดสถานะปัจจุบัน โค้ดแล็บ
  2. สถานะ(TCPSate) - สถานะ
    กำหนดอินเทอร์เฟซสำหรับการห่อหุ้มพฤติกรรมที่เกี่ยวข้องกับสถานะบริบทเฉพาะ แหล่งที่มาของไซต์ ต้นฉบับของไซต์
  3. คลาสย่อย รัฐคอนกรีต(TCPEstablished, TCPListen, TCPClosed) - สถานะเฉพาะ
    แต่ละคลาสย่อยใช้พฤติกรรมที่เกี่ยวข้องกับสถานะบริบทบางอย่าง บริบท. ไซต์ ไซต์ต้นทางดั้งเดิม

โครงการใช้รูปแบบของรัฐ

ระดับ บริบทมอบหมายคำขอไปยังวัตถุปัจจุบัน รัฐคอนกรีต. ไซต์ ไซต์ต้นทางดั้งเดิม

บริบทสามารถส่งผ่านตัวเองเป็นข้อโต้แย้งไปยังวัตถุได้ สถานะซึ่งจะประมวลผลคำขอ สิ่งนี้ทำให้วัตถุสถานะ ( รัฐคอนกรีต) เข้าถึงบริบทหากจำเป็น โค้ดแล็บ

บริบท- นี่คืออินเทอร์เฟซหลักสำหรับลูกค้า ลูกค้าสามารถกำหนดค่าบริบทด้วยวัตถุสถานะ สถานะ(อย่างแม่นยำมากขึ้น รัฐคอนกรีต). เมื่อกำหนดค่าบริบทแล้ว ไคลเอนต์ไม่จำเป็นต้องสื่อสารโดยตรงกับออบเจ็กต์สถานะอีกต่อไป (ผ่านอินเทอร์เฟซทั่วไปเท่านั้น สถานะ). แหล่งที่มา.ru

ในกรณีนี้เช่นกัน บริบทหรือคลาสย่อยเอง รัฐคอนกรีตสามารถตัดสินใจได้ว่าการเปลี่ยนแปลงสถานะจะเกิดขึ้นภายใต้เงื่อนไขใดและในลำดับใด เว็บไซต์ แหล่งที่มาของเว็บไซต์ ต้นฉบับ

คำถามเกี่ยวกับการดำเนินการตามรูปแบบของรัฐ

คำถามเกี่ยวกับการดำเนินการตามรูปแบบของรัฐ: ที่มา original.ru
  1. อะไรเป็นตัวกำหนดการเปลี่ยนแปลงระหว่างรัฐ
    รูปแบบของรัฐไม่ได้พูดอะไรเกี่ยวกับผู้เข้าร่วมที่กำหนดเงื่อนไข (เกณฑ์) สำหรับการเปลี่ยนแปลงระหว่างรัฐ หากเกณฑ์ได้รับการแก้ไขแล้ว ก็สามารถนำไปใช้ในชั้นเรียนได้โดยตรง บริบท. อย่างไรก็ตาม โดยทั่วไปแล้ว แนวทางที่ยืดหยุ่นและถูกต้องมากกว่าคือการอนุญาตให้คลาสย่อยของคลาสเอง สถานะกำหนดสถานะและช่วงเวลาของการเปลี่ยนแปลงครั้งต่อไป เมื่อต้องการทำเช่นนี้ในชั้นเรียน บริบทเราจำเป็นต้องเพิ่มอินเทอร์เฟซที่อนุญาตจากวัตถุ สถานะกำหนดสถานะของมัน
    ตรรกะการเปลี่ยนแปลงแบบกระจายอำนาจนี้ง่ายต่อการแก้ไขและขยาย - คุณเพียงแค่ต้องกำหนดคลาสย่อยใหม่ สถานะ. ข้อเสียของการกระจายอำนาจก็คือแต่ละคลาสย่อย สถานะต้อง "รู้" เกี่ยวกับคลาสย่อยอย่างน้อยหนึ่งคลาสของสถานะอื่น (ซึ่งเขาสามารถเปลี่ยนสถานะปัจจุบันได้จริง) ซึ่งแนะนำการพึ่งพาการใช้งานระหว่างคลาสย่อย แหล่งที่มาของไซต์ ต้นฉบับของไซต์

    ที่มา original.ru
  2. ทางเลือกแบบตาราง
    มีอีกวิธีหนึ่งในการจัดโครงสร้างโค้ดที่ขับเคลื่อนโดยรัฐ นี่คือหลักการของเครื่องจักรสถานะจำกัด ใช้ตารางเพื่อแมปอินพุตกับการเปลี่ยนสถานะ ด้วยความช่วยเหลือนี้ คุณสามารถกำหนดสถานะที่คุณต้องการไปเมื่อข้อมูลอินพุตบางอย่างมาถึง โดยพื้นฐานแล้ว เรากำลังแทนที่โค้ดแบบมีเงื่อนไขด้วยการค้นหาตาราง
    ข้อได้เปรียบหลักของเครื่องคือความสม่ำเสมอ: หากต้องการเปลี่ยนเกณฑ์การเปลี่ยนแปลง ก็เพียงพอที่จะแก้ไขเฉพาะข้อมูล ไม่ใช่โค้ด แต่ก็มีข้อเสียเช่นกัน:
    - การค้นหาตารางมักจะมีประสิทธิภาพน้อยกว่าการเรียกใช้ฟังก์ชัน
    - การนำเสนอตรรกะการเปลี่ยนแปลงในรูปแบบตารางที่เหมือนกันทำให้เกณฑ์มีความชัดเจนน้อยลง และดังนั้นจึงยากต่อการเข้าใจ
    - โดยปกติแล้ว เป็นการยากที่จะเพิ่มการดำเนินการที่มาพร้อมกับการเปลี่ยนระหว่างรัฐ วิธีการแบบตารางคำนึงถึงสถานะและการเปลี่ยนระหว่างสถานะเหล่านั้น แต่จำเป็นต้องเสริมเพื่อให้สามารถคำนวณตามอำเภอใจกับการเปลี่ยนแปลงแต่ละสถานะได้
    ความแตกต่างที่สำคัญระหว่างเครื่องสถานะแบบตารางและสถานะรูปแบบสามารถกำหนดได้ดังนี้: รูปแบบสถานะจำลองพฤติกรรมที่ขึ้นกับสถานะ และวิธีการแบบตารางมุ่งเน้นไปที่การกำหนดการเปลี่ยนระหว่างสถานะ ต้นฉบับ.ru

    ไซต์ ไซต์ต้นทางดั้งเดิม
  3. การสร้างและทำลายวัตถุของรัฐ
    ในระหว่างกระบวนการพัฒนา คุณมักจะต้องเลือกระหว่าง:
    - การสร้างวัตถุสถานะเมื่อจำเป็นและทำลายทันทีหลังการใช้งาน
    - สร้างไว้ล่วงหน้าและตลอดไป

    ตัวเลือกแรกจะดีกว่าเมื่อไม่ทราบล่วงหน้าว่าระบบจะตกอยู่ในสถานะใด และบริบทเปลี่ยนแปลงสถานะค่อนข้างน้อย ในเวลาเดียวกัน เราไม่ได้สร้างออบเจ็กต์ที่จะไม่มีวันถูกใช้ ซึ่งเป็นสิ่งสำคัญหากข้อมูลจำนวนมากถูกเก็บไว้ในออบเจ็กต์สถานะ เมื่อการเปลี่ยนแปลงสถานะเกิดขึ้นบ่อยครั้ง ดังนั้นคุณจึงไม่ต้องการทำลายอ็อบเจ็กต์ที่เป็นตัวแทน (เนื่องจากอาจจำเป็นต้องใช้อีกครั้งในเร็วๆ นี้) คุณควรใช้แนวทางที่สอง เวลาในการสร้างวัตถุจะใช้เวลาเพียงครั้งเดียวในช่วงเริ่มต้นและไม่ได้ใช้เวลาในการทำลายเลย จริงอยู่ วิธีการนี้อาจกลายเป็นเรื่องไม่สะดวก เนื่องจากบริบทจะต้องจัดเก็บการอ้างอิงถึงสถานะทั้งหมดซึ่งระบบอาจเข้าข่ายในทางทฤษฎีได้ ที่มา original.ru

    source.ru ต้นฉบับ
  4. การใช้การเปลี่ยนแปลงแบบไดนามิก
    เป็นไปได้ที่จะเปลี่ยนแปลงพฤติกรรมตามความต้องการโดยการเปลี่ยนคลาสของอ็อบเจ็กต์ขณะรันไทม์ แต่ภาษาเชิงวัตถุส่วนใหญ่ไม่รองรับสิ่งนี้ ข้อยกเว้นคือ Perl, JavaScript และภาษาที่ใช้สคริปต์เอ็นจิ้นอื่นๆ ซึ่งมีกลไกดังกล่าวและรองรับ Pattern State โดยตรง ซึ่งช่วยให้ออบเจ็กต์ปรับเปลี่ยนพฤติกรรมได้โดยการเปลี่ยนรหัสชั้นเรียน แหล่งที่มา.ru

    .ru ต้นฉบับ

ผลลัพธ์

ผลลัพธ์การใช้งาน สถานะรูปแบบ: ที่มาของ original.ru
  1. ปรับพฤติกรรมขึ้นอยู่กับรัฐ
    และแบ่งออกเป็นส่วนที่สอดคล้องกับรัฐ รูปแบบสถานะทำให้พฤติกรรมทั้งหมดที่เกี่ยวข้องกับสถานะใดสถานะหนึ่งเป็นออบเจ็กต์ที่แยกจากกัน เนื่องจากโค้ดที่ขึ้นอยู่กับสถานะมีอยู่ในคลาสย่อยคลาสใดคลาสหนึ่ง สถานะจากนั้นคุณสามารถเพิ่มสถานะและการเปลี่ยนภาพใหม่ได้ง่ายๆ โดยการวางคลาสย่อยใหม่
    เราสามารถใช้สมาชิกข้อมูลเพื่อกำหนดสถานะภายใน จากนั้นจึงดำเนินการของออบเจ็กต์แทน บริบทจะตรวจสอบข้อมูลนี้ แต่ในกรณีนี้ คำสั่งแบบมีเงื่อนไขหรือคำสั่งสาขาที่คล้ายกันจะกระจัดกระจายไปทั่วโค้ดของคลาส บริบท. อย่างไรก็ตาม การเพิ่มสถานะใหม่จะต้องมีการเปลี่ยนแปลงการดำเนินการหลายอย่าง ซึ่งจะทำให้การบำรุงรักษาทำได้ยาก รูปแบบสถานะช่วยแก้ปัญหานี้ แต่ยังสร้างอีกรูปแบบหนึ่ง เนื่องจากพฤติกรรมสำหรับสถานะที่แตกต่างกันจบลงด้วยการกระจายไปยังคลาสย่อยหลายคลาส สถานะ. สิ่งนี้จะเพิ่มจำนวนคลาส แน่นอนว่าคลาสหนึ่งจะมีขนาดกะทัดรัดกว่า แต่ถ้ามีหลายรัฐ การแจกแจงดังกล่าวจะมีประสิทธิภาพมากกว่า เนื่องจากไม่เช่นนั้นจะต้องจัดการกับคำสั่งที่มีเงื่อนไขที่ยุ่งยาก
    การมีเงื่อนไขที่ยุ่งยากเป็นสิ่งที่ไม่พึงประสงค์ เช่นเดียวกับการมีขั้นตอนที่ยาวนาน พวกมันมีขนาดใหญ่เกินไป ซึ่งเป็นเหตุผลว่าทำไมการแก้ไขและขยายโค้ดจึงกลายเป็นปัญหา รูปแบบสถานะเสนอวิธีที่ดีกว่าในการจัดโครงสร้างโค้ดที่ขึ้นกับสถานะ ลอจิกที่อธิบายการเปลี่ยนสถานะไม่ได้รวมอยู่ในคำสั่งแบบเสาหินอีกต่อไป ถ้าหรือ สวิตช์แต่กระจายระหว่างคลาสย่อย สถานะ. ด้วยการห่อหุ้มแต่ละการเปลี่ยนแปลงและการดำเนินการไว้ในคลาส สถานะจะกลายเป็นวัตถุที่เต็มเปี่ยม สิ่งนี้ช่วยปรับปรุงโครงสร้างของโค้ดและทำให้วัตถุประสงค์ของมันชัดเจนยิ่งขึ้น ต้นฉบับ.ru
  2. ทำให้การเปลี่ยนผ่านระหว่างรัฐชัดเจน
    หากวัตถุกำหนดสถานะปัจจุบันในแง่ของข้อมูลภายในเท่านั้น การเปลี่ยนระหว่างสถานะจะไม่มีการแสดงที่ชัดเจน ปรากฏเป็นการมอบหมายให้กับตัวแปรบางตัวเท่านั้น การแนะนำออบเจ็กต์ที่แยกจากกันสำหรับสถานะที่แตกต่างกันทำให้การเปลี่ยนมีความชัดเจนมากขึ้น นอกจากนี้วัตถุ สถานะสามารถปกป้องบริบทได้ บริบทจากความไม่ตรงกันของตัวแปรภายใน เนื่องจากการเปลี่ยนจากมุมมองของบริบทเป็นการกระทำแบบอะตอมมิก หากต้องการทำการเปลี่ยนแปลง คุณต้องเปลี่ยนค่าของตัวแปรเพียงตัวเดียวเท่านั้น (ตัวแปร object สถานะในชั้นเรียน บริบท) แทนที่จะเป็นหลายรายการ แหล่งที่มา.ru
  3. สามารถแชร์วัตถุสถานะได้
    หากอยู่ในวัตถุสถานะ สถานะไม่มีตัวแปรอินสแตนซ์ หมายความว่าสถานะที่แสดงนั้นจะถูกเข้ารหัสตามประเภทเท่านั้น จากนั้นบริบทที่แตกต่างกันก็สามารถแชร์ออบเจ็กต์เดียวกันได้ สถานะ. เมื่อรัฐถูกแยกออกจากกันในลักษณะนี้ โดยพื้นฐานแล้วรัฐเหล่านั้นจะเป็นผู้ฉวยโอกาส (ดูรูปแบบลัทธิฉวยโอกาส) ซึ่งไม่มีสถานะภายใน มีเพียงพฤติกรรมเท่านั้น แหล่งที่มาของไซต์ ไซต์ดั้งเดิม

ตัวอย่าง

ลองดูที่การนำตัวอย่างไปใช้จากส่วน "" เช่น สร้างสถาปัตยกรรมการเชื่อมต่อ TCP อย่างง่าย นี่เป็นโปรโตคอล TCP เวอร์ชันที่เรียบง่าย แน่นอนว่า มันไม่ได้เป็นตัวแทนของโปรโตคอลทั้งหมดหรือแม้แต่สถานะของการเชื่อมต่อ TCP ทั้งหมด source.ru ต้นฉบับ

ก่อนอื่น เรามากำหนดคลาสกันก่อน TCPConnectionซึ่งมีอินเทอร์เฟซสำหรับการถ่ายโอนข้อมูลและจัดการคำขอเปลี่ยนสถานะ: TCPConnection source.ru ต้นฉบับ

ในตัวแปรสมาชิก สถานะระดับ TCPConnectionอินสแตนซ์ของคลาสถูกเก็บไว้ TCPState. คลาสนี้ทำซ้ำอินเทอร์เฟซการเปลี่ยนแปลงสถานะที่กำหนดไว้ในคลาส TCPConnection. ที่มาของ original.ru

ไซต์ ไซต์ต้นทางดั้งเดิม

TCPConnectionมอบหมายคำขอที่ขึ้นอยู่กับสถานะทั้งหมดไปยังอินสแตนซ์ที่เก็บไว้ในสถานะ TCPState. ในชั้นเรียนอีกด้วย TCPConnectionมีการผ่าตัด เปลี่ยนสถานะซึ่งคุณสามารถเขียนตัวชี้ไปยังวัตถุอื่นลงในตัวแปรนี้ได้ TCPState. ตัวสร้างคลาส TCPConnectionเริ่มต้น สถานะตัวชี้ไปยังสถานะปิด TCPปิดแล้ว(เราจะกำหนดไว้ด้านล่าง) ที่มา original.ru

source.ru ต้นฉบับ

ทุกการดำเนินการ TCPStateยอมรับอินสแตนซ์ TCPConnectionเป็นพารามิเตอร์จึงอนุญาตให้วัตถุ TCPStateเข้าถึงข้อมูลวัตถุ TCPConnectionและเปลี่ยนสถานะการเชื่อมต่อ .ru

ในชั้นเรียน TCPStateใช้พฤติกรรมเริ่มต้นสำหรับคำขอทั้งหมดที่ได้รับมอบหมาย นอกจากนี้ยังสามารถเปลี่ยนสถานะของวัตถุได้อีกด้วย TCPConnectionผ่านการผ่าตัด เปลี่ยนสถานะ. TCPStateอยู่ในแพ็คเกจเดียวกันกับ TCPConnectionดังนั้นจึงสามารถเข้าถึงการดำเนินการนี้: TCPState ไซต์ ไซต์ต้นทางดั้งเดิม

ต้นฉบับ.ru

ในคลาสย่อย TCPStateมีการใช้พฤติกรรมที่ขึ้นอยู่กับรัฐ การเชื่อมต่อ TCP สามารถมีได้หลายสถานะ: ที่จัดตั้งขึ้น(ติดตั้ง), การฟัง(การฟัง), ปิด(ปิด) ฯลฯ และแต่ละอันก็มีคลาสย่อยของตัวเอง TCPState. เพื่อความง่ายเราจะพิจารณารายละเอียดเพียง 3 คลาสย่อยเท่านั้น - TCP ก่อตั้งแล้ว, TCPListenและ TCPปิดแล้ว. เว็บไซต์ เว็บไซต์ แหล่งที่มาดั้งเดิม

ที่มา รุ

ในคลาสย่อย TCPStateใช้พฤติกรรมที่ขึ้นอยู่กับสถานะสำหรับคำขอเหล่านั้นที่ถูกต้องในสถานะนั้น .ru

ไซต์ ไซต์ต้นทางดั้งเดิม

หลังจากดำเนินการเฉพาะของรัฐแล้ว การดำเนินการเหล่านี้ ที่มา original.ru

สาเหตุ เปลี่ยนสถานะเพื่อเปลี่ยนสถานะของวัตถุ TCPConnection. ตัวเขาเองไม่มีข้อมูลเกี่ยวกับโปรโตคอล TCP มันเป็นคลาสย่อย TCPStateกำหนดช่วงการเปลี่ยนภาพระหว่างสถานะและการดำเนินการที่กำหนดโดยโปรโตคอล ต้นฉบับ.ru

ที่มา original.ru

การประยุกต์รูปแบบของรัฐที่เป็นที่รู้จัก

Ralph Johnson และ Jonathan Zweig แสดงลักษณะเฉพาะของรูปแบบสถานะและอธิบายโดยสัมพันธ์กับโปรโตคอล TCP
โปรแกรมวาดภาพเชิงโต้ตอบที่ได้รับความนิยมส่วนใหญ่มี "เครื่องมือ" สำหรับดำเนินการจัดการโดยตรง ตัวอย่างเช่น เครื่องมือวาดเส้นอนุญาตให้ผู้ใช้คลิกที่จุดที่ต้องการด้วยเมาส์ จากนั้นเลื่อนเมาส์เพื่อวาดเส้นจากจุดนั้น เครื่องมือการเลือกช่วยให้คุณเลือกรูปร่างบางอย่างได้ โดยทั่วไปแล้ว เครื่องมือที่มีอยู่ทั้งหมดจะอยู่ในพาเล็ต งานของผู้ใช้คือการเลือกและใช้เครื่องมือ แต่ในความเป็นจริงแล้ว พฤติกรรมของโปรแกรมแก้ไขจะแตกต่างกันไปตามการเปลี่ยนแปลงของเครื่องมือ: ด้วยเครื่องมือวาดภาพที่เราสร้างรูปร่าง ด้วยเครื่องมือการเลือกที่เราเลือก และอื่นๆ เว็บไซต์ เว็บไซต์ แหล่งที่มาดั้งเดิม

เพื่อสะท้อนถึงการพึ่งพาพฤติกรรมของบรรณาธิการกับเครื่องมือปัจจุบัน คุณสามารถใช้รูปแบบสถานะได้ ที่มาต้นฉบับ.ru

คุณสามารถกำหนดคลาสนามธรรมได้ เครื่องมือซึ่งคลาสย่อยใช้พฤติกรรมเฉพาะของเครื่องมือ ตัวแก้ไขกราฟิกจะจัดเก็บลิงก์ไปยังวัตถุปัจจุบัน ด้วยและมอบหมายคำขอที่เข้ามาให้เขา เมื่อคุณเลือกเครื่องมือ ตัวแก้ไขจะใช้ออบเจ็กต์อื่น ซึ่งทำให้ลักษณะการทำงานเปลี่ยนไป .ru

เทคนิคนี้ใช้ในเฟรมเวิร์กของโปรแกรมแก้ไขกราฟิก HotDraw และ Unidraw ช่วยให้ลูกค้ากำหนดเครื่องมือประเภทใหม่ได้อย่างง่ายดาย ใน ฮอทดรอว์ระดับ ตัวควบคุมการวาดภาพส่งต่อคำขอไปยังวัตถุปัจจุบัน เครื่องมือ. ใน Unidrawคลาสที่เกี่ยวข้องจะถูกเรียก ผู้ดูและ เครื่องมือ. แผนภาพคลาสด้านล่างแสดงแผนผังของอินเทอร์เฟซคลาส เครื่องมือ

ต้นฉบับ.ru

วัตถุประสงค์ของแบบแผนของรัฐ

  • รูปแบบสถานะอนุญาตให้วัตถุเปลี่ยนพฤติกรรมขึ้นอยู่กับสถานะภายใน ปรากฏว่าวัตถุได้เปลี่ยนคลาสของมัน
  • รูปแบบสถานะคือการดำเนินการเชิงวัตถุของเครื่องสถานะ

ปัญหาที่ต้องแก้ไข

พฤติกรรมของอ็อบเจ็กต์ขึ้นอยู่กับสถานะและต้องเปลี่ยนแปลงระหว่างการทำงานของโปรแกรม โครงการดังกล่าวสามารถนำไปใช้ได้โดยใช้ตัวดำเนินการตามเงื่อนไขหลายตัว: การดำเนินการบางอย่างจะดำเนินการตามการวิเคราะห์สถานะปัจจุบันของวัตถุ อย่างไรก็ตาม ด้วยสถานะจำนวนมาก ข้อความสั่งแบบมีเงื่อนไขจะกระจัดกระจายไปทั่วโค้ด และโปรแกรมดังกล่าวจะรักษาได้ยาก

การอภิปรายรูปแบบของรัฐ

รูปแบบของรัฐแก้ไขปัญหานี้ดังนี้:

  • แนะนำคลาสบริบทที่กำหนดส่วนต่อประสานกับโลกภายนอก
  • แนะนำคลาสรัฐนามธรรม
  • แสดงถึง "สถานะ" ต่างๆ ของเครื่องสถานะเป็นคลาสย่อยของรัฐ
  • คลาสบริบทมีตัวชี้ไปยังสถานะปัจจุบันที่เปลี่ยนแปลงเมื่อสถานะของเครื่องสถานะเปลี่ยนแปลง

รูปแบบสถานะไม่ได้กำหนดว่าเงื่อนไขในการเปลี่ยนไปสู่สถานะใหม่ถูกกำหนดไว้ที่ใด มีสองตัวเลือก: คลาสบริบทหรือคลาสย่อยของรัฐ ข้อดีของตัวเลือกหลังคือสามารถเพิ่มคลาสที่ได้รับใหม่ๆ ได้อย่างง่ายดาย ข้อเสียคือแต่ละคลาสย่อยของ State จะต้องรู้เกี่ยวกับเพื่อนบ้านของตนเพื่อทำการเปลี่ยนผ่านไปสู่สถานะใหม่ ซึ่งทำให้เกิดการพึ่งพาระหว่างคลาสย่อย

นอกจากนี้ยังมีแนวทางอื่นที่ใช้ตารางในการออกแบบเครื่องจักรสถานะจำกัด โดยอิงจากการใช้ตารางที่แมปข้อมูลอินพุตกับการเปลี่ยนระหว่างสถานะโดยไม่ซ้ำกัน อย่างไรก็ตาม วิธีการนี้มีข้อเสีย: เป็นการยากที่จะเพิ่มการดำเนินการเมื่อดำเนินการเปลี่ยนภาพ แนวทางรูปแบบสถานะใช้โค้ด (แทนโครงสร้างข้อมูล) เพื่อทำการเปลี่ยนระหว่างสถานะ ดังนั้นจึงเพิ่มการดำเนินการเหล่านี้ได้ง่าย

โครงสร้างรูปแบบของรัฐ

คลาสบริบทกำหนดอินเทอร์เฟซภายนอกสำหรับไคลเอนต์และจัดเก็บการอ้างอิงถึงสถานะปัจจุบันของวัตถุสถานะ อินเทอร์เฟซของสถานะคลาสฐานนามธรรมจะเหมือนกับอินเทอร์เฟซบริบท ยกเว้นพารามิเตอร์เพิ่มเติมหนึ่งรายการ - ตัวชี้ไปยังอินสแตนซ์บริบท คลาสที่ได้มาจากรัฐจะกำหนดพฤติกรรมเฉพาะของรัฐ คลาส Wrapper บริบทมอบหมายคำขอที่ได้รับทั้งหมดให้กับออบเจ็กต์ "สถานะปัจจุบัน" ซึ่งสามารถใช้พารามิเตอร์เพิ่มเติมที่ได้รับเพื่อเข้าถึงอินสแตนซ์บริบท

รูปแบบสถานะอนุญาตให้วัตถุเปลี่ยนพฤติกรรมขึ้นอยู่กับสถานะภายใน ภาพที่คล้ายกันสามารถสังเกตได้ในการทำงานของเครื่องจำหน่ายสินค้าอัตโนมัติ เครื่องสามารถมีสถานะที่แตกต่างกันได้ขึ้นอยู่กับความพร้อมของสินค้า, จำนวนเหรียญที่ได้รับ, ความสามารถในการแลกเปลี่ยนเงิน ฯลฯ หลังจากที่ผู้ซื้อเลือกและชำระค่าสินค้าแล้ว สถานการณ์ (รัฐ) ต่อไปนี้จะเป็นไปได้:

  • มอบสินค้าให้กับผู้ซื้อ ไม่จำเป็นต้องเปลี่ยนแปลง
  • ให้ผู้ซื้อสินค้าและการเปลี่ยนแปลง
  • ผู้ซื้อจะไม่ได้รับสินค้าเนื่องจากไม่มีเงินเพียงพอ
  • ผู้ซื้อจะไม่ได้รับสินค้าเนื่องจากไม่มีสินค้า

การใช้รูปแบบของรัฐ

  • กำหนดคลาส wrapper บริบทที่มีอยู่หรือสร้างใหม่เพื่อให้ไคลเอ็นต์ใช้เป็น "เครื่องสถานะ"
  • สร้างคลาสสถานะพื้นฐานที่จำลองแบบอินเทอร์เฟซของคลาสบริบท แต่ละวิธีใช้พารามิเตอร์เพิ่มเติมหนึ่งตัว: อินสแตนซ์ของคลาสบริบท คลาส State สามารถกำหนดพฤติกรรม "ค่าเริ่มต้น" ที่เป็นประโยชน์ได้
  • สร้างคลาสที่ได้มาจากรัฐสำหรับรัฐที่เป็นไปได้ทั้งหมด
  • คลาส wrapper บริบทมีการอ้างอิงถึงวัตถุสถานะปัจจุบัน
  • คลาสบริบทเพียงมอบหมายคำขอทั้งหมดที่ได้รับจากไคลเอนต์ไปยังวัตถุ "สถานะปัจจุบัน" โดยมีที่อยู่ของวัตถุบริบทที่ถูกส่งผ่านเป็นพารามิเตอร์เพิ่มเติม
  • การใช้ที่อยู่นี้ วิธีการของคลาส State สามารถเปลี่ยน "สถานะปัจจุบัน" ของคลาส Context ได้หากจำเป็น

คุณสมบัติของรูปแบบของรัฐ

  • วัตถุของรัฐมักจะเป็นซิงเกิลตัน
  • รุ่นฟลายเวทแสดงให้เห็นว่าวัตถุสถานะสามารถแยกได้อย่างไรและเมื่อใด
  • รูปแบบล่ามสามารถใช้สถานะเพื่อกำหนดบริบทการแยกวิเคราะห์
  • รูปแบบ State และ Bridge มีโครงสร้างที่คล้ายกัน ยกเว้นว่า Bridge อนุญาตให้มีลำดับชั้นของคลาสซองจดหมาย (อะนาล็อกของคลาส "wrapper") ในขณะที่ State ไม่อนุญาต รูปแบบเหล่านี้มีโครงสร้างที่คล้ายกัน แต่แก้ปัญหาที่แตกต่างกัน: สถานะอนุญาตให้วัตถุเปลี่ยนพฤติกรรมขึ้นอยู่กับสถานะภายใน ในขณะที่ Bridge แยกสิ่งที่เป็นนามธรรมจากการนำไปปฏิบัติเพื่อให้สามารถเปลี่ยนแปลงได้อย่างอิสระจากกัน
  • การดำเนินการตามรูปแบบของรัฐจะขึ้นอยู่กับรูปแบบยุทธศาสตร์ ความแตกต่างอยู่ในจุดประสงค์ของพวกเขา

การดำเนินการตามรูปแบบของรัฐ

ลองพิจารณาตัวอย่างของเครื่องสถานะจำกัดที่มีสถานะที่เป็นไปได้สองสถานะและสองเหตุการณ์

#รวม ใช้เนมสเปซมาตรฐาน; คลาสเครื่อง ( class State *current; public: Machine(); void setCurrent(State *s) ( current = s; ) void on(); void off(); ); สถานะของคลาส ( สาธารณะ: โมฆะเสมือนบน (เครื่อง * m) ( cout<< " already ON\n"; } virtual void off(Machine *m) { cout << " already OFF\n"; } }; void Machine::on() { current->บน(นี้); ) เป็นโมฆะ Machine::off() ( current->off(this); ) class ON: public State ( public: ON() ( cout<< " ON-ctor "; }; ~ON() { cout << " dtor-ON\n"; }; void off(Machine *m); }; class OFF: public State { public: OFF() { cout << " OFF-ctor "; }; ~OFF() { cout << " dtor-OFF\n"; }; void on(Machine *m) { cout << " going from OFF to ON"; m->setCurrent(เปิดใหม่()); ลบสิ่งนี้; ) ); เป็นโมฆะ ON::off(Machine *m) ( cout<< " going from ON to OFF"; m->setCurrent(ปิดใหม่()); ลบสิ่งนี้; ) Machine::Machine() ( ปัจจุบัน = ใหม่ OFF(); cout<< "\n"; } int main() { void(Machine:: *ptrs)() = { Machine::off, Machine::on }; Machine fsm; int num; while (1) { cout << "Enter 0/1: "; cin >>หมายเลข; (fsm. *ptrs)(); ) )

15.02.2016
21:30

รูปแบบสถานะมีไว้สำหรับการออกแบบคลาสที่มีสถานะลอจิคัลอิสระหลายตัว มาดูตัวอย่างกันดีกว่า

สมมติว่าเรากำลังพัฒนาคลาสควบคุมเว็บแคม กล้องสามารถอยู่ในสามสถานะ:

  1. ไม่ได้เตรียมใช้งาน เรียกมันว่า NotConnectedState ;
  2. เริ่มต้นและพร้อมใช้งานแล้ว แต่ยังไม่มีการบันทึกเฟรมใดๆ ปล่อยให้มันเป็น ReadyState ;
  3. โหมดจับภาพเฟรมที่ใช้งานอยู่ มาแสดง ActiveState กัน

เนื่องจากเรากำลังทำงานกับรูปแบบของรัฐ วิธีที่ดีที่สุดคือเริ่มต้นด้วยภาพแผนภาพรัฐ:

ตอนนี้เรามาเปลี่ยนไดอะแกรมนี้เป็นโค้ดกัน เพื่อไม่ให้การใช้งานยุ่งยาก เราจึงละเว้นโค้ดสำหรับการทำงานกับกล้องเว็บ หากจำเป็น คุณสามารถเพิ่มฟังก์ชันไลบรารีที่เหมาะสมได้ด้วยตัวเอง

ฉันจะให้ข้อมูลรายการทั้งหมดพร้อมความคิดเห็นเพียงเล็กน้อยทันที ต่อไปเราจะหารือรายละเอียดที่สำคัญของการดำเนินการนี้โดยละเอียดยิ่งขึ้น

#รวม #define DECLARE_GET_INSTANCE(ClassName) \ static ClassName* getInstance() (\ static ClassName instance;\ return \ ) คลาส WebCamera ( public: typedef std::string Frame; public: // *********** *************************************** // ข้อยกเว้น // ****** ******************************************** คลาสไม่รองรับ: สาธารณะมาตรฐาน: :ข้อยกเว้น ( ); สาธารณะ: // ***************************************** ******* ********* // รัฐ // ***************************** ******* ************** คลาส NotConnectedState; คลาส ReadyState; คลาส ActiveState; สถานะคลาส ( สาธารณะ: เสมือน ~State() ( ) การเชื่อมต่อโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) การตัดการเชื่อมต่อโมฆะเสมือน (WebCamera* cam) ( std::cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState(NotConnectedState::getInstance()); ) การเริ่มต้นเป็นโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) หยุดโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) เฟรมเสมือน getFrame(WebCamera*) ( โยน NotSupported(); ) ป้องกัน: รัฐ() ( ) ); // ************************************************ ** คลาส NotConnectedState: สถานะสาธารณะ ( สาธารณะ: DECLARE_GET_INSTANCE(NotConnectedState) การเชื่อมต่อเป็นโมฆะ (กล้อง WebCamera*) ( std::cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) ยกเลิกการเชื่อมต่อเป็นโมฆะ (WebCamera*) ( โยน NotSupported(); ) ส่วนตัว: NotConnectedState() ( ) ); // ************************************************ ** คลาส ReadyState: สถานะสาธารณะ ( สาธารณะ: DECLARE_GET_INSTANCE(ReadyState) การเริ่มต้นเป็นโมฆะ (กล้อง WebCamera*) ( std::cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState(ActiveState::getInstance()); ) ส่วนตัว: ReadyState() ( ) ); // ************************************************ ** คลาส ActiveState: สถานะสาธารณะ ( สาธารณะ: DECLARE_GET_INSTANCE(ActiveState) หยุดเป็นโมฆะ (กล้อง WebCamera*) ( std::cout<< "Останавливаем видео-поток..." << std::endl; // ... cam-> << "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } }; public: explicit WebCamera(int camID) : m_camID(camID), m_state(NotConnectedState::getInstance()) { } ~WebCamera() { try { disconnect(); } catch(const NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } } void connect() { m_state->เชื่อมต่อ(นี้); ) โมฆะตัดการเชื่อมต่อ() ( m_state->disconnect(this); ) void start() ( m_state->start(this); ) void stop() ( m_state->stop(this); ) Frame getFrame() ( return m_state ->getFrame(this); ) ส่วนตัว: void changeState(State* newState) ( m_state = newState; ) ส่วนตัว: int m_camID; รัฐ* m_state; );

ฉันดึงความสนใจของคุณไปที่มาโคร DECLARE_GET_INSTANCE แน่นอนว่าเราไม่สนับสนุนการใช้มาโครใน C++ อย่างไรก็ตาม สิ่งนี้ใช้กับกรณีที่แมโครทำหน้าที่เป็นแอนะล็อกของฟังก์ชันเทมเพลต ในกรณีนี้ ควรให้ความสำคัญกับอย่างหลังเสมอ

ในกรณีของเรา มาโครมีวัตถุประสงค์เพื่อกำหนดฟังก์ชันคงที่ที่จำเป็นในการใช้งาน ดังนั้นการใช้งานจึงถือว่าสมเหตุสมผล ท้ายที่สุดแล้ว มันช่วยให้คุณลดความซ้ำซ้อนของโค้ดและไม่ก่อให้เกิดภัยคุกคามร้ายแรงใดๆ

เราประกาศคลาส State ในคลาสหลัก - WebCamera เพื่อความกระชับ ฉันใช้คำจำกัดความแบบอินไลน์ของฟังก์ชันสมาชิกของทุกคลาส อย่างไรก็ตาม ในแอปพลิเคชันจริง ควรปฏิบัติตามคำแนะนำเกี่ยวกับการแยกการประกาศและการนำไปใช้งานออกเป็นไฟล์ h และ cpp จะดีกว่า

คลาสสถานะจะถูกประกาศภายใน WebCamera เพื่อให้สามารถเข้าถึงฟิลด์ส่วนตัวของคลาสนั้นได้ แน่นอนว่าสิ่งนี้จะสร้างความสัมพันธ์ที่แน่นแฟ้นระหว่างคลาสเหล่านี้ทั้งหมด แต่รัฐกลับกลายเป็นว่ามีความเฉพาะเจาะจงมากจนการใช้ซ้ำในบริบทอื่นไม่เป็นคำถาม

พื้นฐานของลำดับชั้นของคลาสสถานะคือคลาสนามธรรม WebCamera::State:

สถานะของคลาส ( สาธารณะ: virtual ~State() ( ) การเชื่อมต่อโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) การตัดการเชื่อมต่อโมฆะเสมือน (WebCamera* cam) ( std::cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState(NotConnectedState::getInstance()); ) การเริ่มต้นเป็นโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) หยุดเป็นโมฆะเสมือน (WebCamera*) ( โยน NotSupported(); ) เฟรมเสมือน getFrame(WebCamera*) ( โยน NotSupported(); ) ป้องกัน: รัฐ() ( ) );

ฟังก์ชั่นสมาชิกทั้งหมดสอดคล้องกับฟังก์ชั่นของคลาส WebCamera เอง การมอบหมายโดยตรงเกิดขึ้น:

Class WebCamera ( // ... เป็นโมฆะเชื่อมต่อ() ( m_state->เชื่อมต่อ (นี้); ) เป็นโมฆะตัดการเชื่อมต่อ () ( m_state->ตัดการเชื่อมต่อ (นี้); ) เป็นโมฆะเริ่มต้น () ( m_state->เริ่มต้น (นี้); ) เป็นโมฆะหยุด() ( m_state->หยุด(นี้); ) เฟรม getFrame() ( กลับ m_state->getFrame(นี้); ) // ... สถานะ* m_state; )

คุณลักษณะที่สำคัญคือวัตถุสถานะยอมรับตัวชี้ไปยังอินสแตนซ์ WebCamera ที่เรียกมัน วิธีนี้ช่วยให้คุณมีวัตถุสถานะเพียงสามรายการสำหรับกล้องจำนวนมากโดยพลการ ความเป็นไปได้นี้เกิดขึ้นได้จากการใช้รูปแบบซิงเกิลตัน แน่นอน ในบริบทของตัวอย่าง คุณจะไม่ได้รับผลประโยชน์ที่สำคัญจากสิ่งนี้ แต่การรู้เทคนิคนี้ก็ยังมีประโยชน์

โดยตัวมันเองแล้ว คลาส WebCamera แทบจะไม่ทำอะไรเลย เขาต้องพึ่งพารัฐของเขาโดยสิ้นเชิง และรัฐเหล่านี้จะกำหนดเงื่อนไขในการปฏิบัติงานและให้บริบทที่จำเป็น

ฟังก์ชั่นสมาชิก WebCamera::State ส่วนใหญ่โยน WebCamera::NotSupported ของเราเอง นี่เป็นพฤติกรรมเริ่มต้นที่เหมาะสมอย่างยิ่ง ตัวอย่างเช่น หากมีคนพยายามกำหนดค่าเริ่มต้นให้กับกล้องเมื่อกล้องถูกกำหนดค่าเริ่มต้นแล้ว พวกเขาจะได้รับข้อยกเว้นโดยธรรมชาติ

ในเวลาเดียวกัน เรามีการใช้งานเริ่มต้นสำหรับ WebCamera::State::disconnect() ลักษณะการทำงานนี้เหมาะสำหรับสองในสามรัฐ ด้วยเหตุนี้ เราจึงป้องกันการทำซ้ำโค้ด

หากต้องการเปลี่ยนสถานะ ให้ใช้ฟังก์ชันสมาชิกส่วนตัว WebCamera::changeState() :

โมฆะ changeState (สถานะ * newState) ( m_state = newState; )

ตอนนี้ถึงการดำเนินการของรัฐเฉพาะ สำหรับ WebCamera::NotConnectedState การแทนที่การดำเนินการเชื่อมต่อ() และตัดการเชื่อมต่อ() ก็เพียงพอแล้ว:

Class NotConnectedState: public State ( public: DECLARE_GET_INSTANCE(NotConnectedState) การเชื่อมต่อเป็นโมฆะ (WebCamera* cam) ( std::cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) ยกเลิกการเชื่อมต่อเป็นโมฆะ (WebCamera*) ( โยน NotSupported(); ) ส่วนตัว: NotConnectedState() ( ) );

สำหรับแต่ละรัฐ คุณสามารถสร้างอินสแตนซ์เดียวได้ สิ่งนี้รับประกันแก่เราโดยการประกาศตัวสร้างส่วนตัว

องค์ประกอบที่สำคัญอีกประการหนึ่งของการดำเนินการที่นำเสนอคือ เราจะย้ายไปยังสถานะใหม่หากประสบความสำเร็จเท่านั้น ตัวอย่างเช่น หากเกิดความล้มเหลวระหว่างการเริ่มต้นกล้อง ยังเร็วเกินไปที่จะเข้าสู่ ReadyState แนวคิดหลักคือความสอดคล้องที่สมบูรณ์ระหว่างสถานะที่แท้จริงของกล้อง (ในกรณีของเรา) และวัตถุสถานะ

ดังนั้นกล้องก็พร้อมที่จะไป มาสร้างคลาส WebCamera::ReadyState State ที่สอดคล้องกัน:

Class ReadyState: public State ( public: DECLARE_GET_INSTANCE(ReadyState) void start (WebCamera* cam) ( std::cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState(ActiveState::getInstance()); ) ส่วนตัว: ReadyState() ( ) );

จากสถานะพร้อม เราสามารถเข้าสู่สถานะเฟรมจับภาพที่ใช้งานอยู่ได้ เพื่อจุดประสงค์นี้ จึงมีการดำเนินการ start() ซึ่งเราได้นำไปใช้งาน

ในที่สุดเราก็มาถึงสถานะลอจิคัลสุดท้ายของกล้องแล้ว WebCamera::ActiveState:

คลาส ActiveState: สถานะสาธารณะ (สาธารณะ: DECLARE_GET_INSTANCE (ActiveState) หยุดเป็นโมฆะ (WebCamera* cam) ( std::cout<< "Останавливаем видео-поток..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) เฟรม getFrame(WebCamera*) ( std::cout<< "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } };

ในสถานะนี้ คุณสามารถหยุดการจับเฟรมได้โดยใช้ stop() ด้วยเหตุนี้ เราจะถูกนำกลับไปที่ WebCamera::ReadyState นอกจากนี้เรายังสามารถรับเฟรมที่สะสมอยู่ในบัฟเฟอร์ของกล้องได้อีกด้วย เพื่อความง่าย คำว่า "frame" เราหมายถึงสตริงปกติ ในความเป็นจริง นี่จะเป็นอาร์เรย์ไบต์บางประเภท

ตอนนี้เราสามารถเขียนตัวอย่างทั่วไปของการทำงานกับคลาส WebCamera ของเราได้แล้ว:

Int main() ( WebCamera cam(0); ลอง ( // cam ใน NotConnectedState cam.connect(); // cam ใน ReadyState cam.start(); // cam ใน ActiveState std::cout<< cam.getFrame() << std::endl; cam.stop(); // Можно было сразу вызвать disconnect() // cam в Состоянии ReadyState cam.disconnect(); // cam в Состоянии NotConnectedState } catch(const WebCamera::NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } return 0; }

นี่คือสิ่งที่จะส่งออกไปยังคอนโซล:

เริ่มต้นกล้อง... เริ่มสตรีมวิดีโอ... รับเฟรมปัจจุบัน... เฟรมปัจจุบัน หยุดสตรีมวิดีโอ... ยกเลิกการเริ่มต้นกล้อง...

ตอนนี้เรามาลองกระตุ้นให้เกิดข้อผิดพลาด มาเรียกเชื่อมต่อ () สองครั้งติดต่อกัน:

Int main() ( WebCamera cam(0); try ( // cam ใน NotConnectedState cam.connect(); // cam ใน ReadyState // แต่สำหรับสถานะนี้ จะไม่มีการดำเนินการเชื่อมต่อ ()! cam.connect( ); // ส่งข้อยกเว้น NotSupported ) catch(const WebCamera::NotSupported& e) ( std::cout<< "Произошло исключение!!!" << std::endl; // ... } catch(...) { // Обрабатываем исключение } return 0; }

นี่คือสิ่งที่ออกมาจากมัน:

กำลังเริ่มต้นกล้อง... มีข้อยกเว้นเกิดขึ้น!!! มายกเลิกการตั้งค่าเริ่มต้นของกล้องกันเถอะ...

โปรดทราบว่ากล้องยังคงถูกยกเลิกการกำหนดค่าเริ่มต้น การเรียกdisconnect()เกิดขึ้นในตัวทำลาย WebCamera เหล่านั้น. สถานะภายในของวัตถุยังคงถูกต้องอย่างแน่นอน

ข้อสรุป

เมื่อใช้รูปแบบสถานะ คุณสามารถแปลงแผนภาพสถานะให้เป็นโค้ดได้โดยไม่ซ้ำกัน เมื่อมองแวบแรก การนำไปปฏิบัติกลายเป็นเรื่องละเอียด อย่างไรก็ตาม เราได้แบ่งอย่างชัดเจนตามบริบทที่เป็นไปได้สำหรับการทำงานกับ WebCamera ระดับหลัก ด้วยเหตุนี้ เมื่อเขียนแต่ละรัฐ เราก็สามารถมุ่งความสนใจไปที่งานแคบๆ ได้ และนี่คือวิธีที่ดีที่สุดในการเขียนโค้ดที่ชัดเจน เข้าใจได้ และเชื่อถือได้