สถานะ. ชั้นเรียนของรัฐสำหรับแต่ละรัฐ
รูปแบบการออกแบบพฤติกรรม มันถูกใช้ในกรณีที่ระหว่างการทำงานของโปรแกรม อ็อบเจ็กต์ต้องเปลี่ยนพฤติกรรมของมันขึ้นอยู่กับสถานะของมัน การใช้งานแบบคลาสสิกเกี่ยวข้องกับการสร้างคลาสนามธรรมพื้นฐานหรืออินเทอร์เฟซที่มีวิธีการทั้งหมดและหนึ่งคลาสสำหรับแต่ละสถานะที่เป็นไปได้ รูปแบบนี้เป็นกรณีพิเศษของคำแนะนำ "แทนที่ข้อความสั่งแบบมีเงื่อนไขด้วยความหลากหลาย"
ดูเหมือนว่าทุกอย่างเป็นไปตามหนังสือ แต่มีความแตกต่างกันนิดหน่อย จะใช้วิธีการที่ถูกต้องที่ไม่เกี่ยวข้องกับสถานะที่กำหนดได้อย่างไร ตัวอย่างเช่น จะลบสินค้าออกจากรถเข็นเปล่าหรือชำระค่ารถเข็นเปล่าได้อย่างไร? โดยทั่วไป แต่ละคลาสของรัฐใช้วิธีการที่เกี่ยวข้องเท่านั้น และจะส่ง 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สาขาวิชา
มาประกาศเอนทิตี "รถเข็น":อินเทอร์เฟซสาธารณะ IHasState
เราจะใช้หนึ่งคลาสสำหรับสถานะรถเข็นแต่ละรัฐ: ว่างเปล่า ใช้งานอยู่ และชำระเงินแล้ว แต่เราจะไม่ประกาศอินเทอร์เฟซทั่วไป ปล่อยให้แต่ละรัฐใช้เฉพาะพฤติกรรมที่เกี่ยวข้องเท่านั้น นี่ไม่ได้หมายความว่าคลาส EmptyCartState, ActiveCartState และ PaidCartState ไม่สามารถใช้อินเทอร์เฟซเดียวกันทั้งหมดได้ สามารถทำได้ แต่อินเทอร์เฟซดังกล่าวต้องมีเฉพาะวิธีการที่มีอยู่ในแต่ละสถานะเท่านั้น ในกรณีของเรา วิธีการเพิ่มมีอยู่ใน EmptyCartState และ ActiveCartState ดังนั้นเราจึงสามารถสืบทอดวิธีการเหล่านี้จาก AddableCartStateBase แบบนามธรรม อย่างไรก็ตาม คุณสามารถเพิ่มสินค้าลงในรถเข็นที่ยังไม่ได้ชำระเงินได้เท่านั้น ดังนั้นจึงไม่มีอินเทอร์เฟซทั่วไปสำหรับทุกรัฐ ด้วยวิธีนี้เรารับประกันได้ว่าไม่มี InvalidOperationException ในโค้ดของเราในขณะคอมไพล์
รถเข็นคลาสสาธารณะบางส่วน (สาธารณะ enum CartStateCode: ไบต์ (ว่างเปล่า, ใช้งานอยู่, ชำระเงิน) อินเทอร์เฟซสาธารณะ IAddableCartState ( ActiveCartState เพิ่ม (ผลิตภัณฑ์ผลิตภัณฑ์); IEnumerable
รัฐถูกประกาศว่าซ้อนกัน ( ซ้อนกัน) ชั้นเรียนไม่ใช่เรื่องบังเอิญ คลาสที่ซ้อนกันสามารถเข้าถึงสมาชิกที่ได้รับการป้องกันของคลาส Cart ซึ่งหมายความว่าเราไม่จำเป็นต้องเสียสละการห่อหุ้มเอนทิตีเพื่อนำพฤติกรรมไปใช้ เพื่อหลีกเลี่ยงความยุ่งเหยิงในไฟล์คลาสเอนทิตี ฉันจึงแยกการประกาศออกเป็นสองส่วน: Cart.cs และ CartStates.cs โดยใช้คีย์เวิร์ดบางส่วน
ผลการดำเนินการสาธารณะ GetViewResult (สถานะ
เราจะใช้มุมมองที่แตกต่างกันขึ้นอยู่กับสถานะของรถเข็น สำหรับรถเข็นเปล่า เราจะแสดงข้อความ “รถเข็นของคุณว่างเปล่า” รถเข็นที่ใช้งานอยู่จะมีรายการผลิตภัณฑ์ ความสามารถในการเปลี่ยนจำนวนผลิตภัณฑ์และลบบางส่วนออก ปุ่ม "สั่งซื้อ" และจำนวนการซื้อทั้งหมด
รถเข็นที่ชำระเงินจะมีลักษณะเหมือนกับรถเข็นที่ใช้งานอยู่ แต่ไม่มีความสามารถในการแก้ไขสิ่งใดๆ ข้อเท็จจริงนี้สามารถสังเกตได้โดยการเน้นอินเทอร์เฟซ 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... ) ) )เป็นโมฆะ Heroine::handleInput (อินพุตอินพุต) ( ถ้า (อินพุต == PRESS_B) ( //โดดเลยถ้ายังไม่ได้...) else if (input == PRESS_DOWN) ( if (!isJumping_) ( setGraphics(IMAGE_DUCK); ) ) else if (input == RELEASE_DOWN) ( setGraphics(IMAGE_STAND); ) )นอกจากนี้เรายังต้องการโค้ดที่จะตั้งค่า isJumping_ กลับเป็น false เมื่อนางเอกแตะพื้นอีกครั้ง เพื่อความง่าย ฉันไม่ใส่รหัสนี้
คุณสังเกตเห็นข้อผิดพลาดที่นี่หรือไม่?
ด้วยรหัสนี้ ผู้เล่นสามารถ:
- กดลงไปหมอบ
- กด B เพื่อกระโดดจากท่านั่ง
- ปล่อยลงขณะอยู่ในอากาศ
ขณะเดียวกันนางเอกจะเปลี่ยนไปใช้กราฟิคการยืนอยู่กลางอากาศ เราจะต้องเพิ่มธงอื่น ...
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- เมื่อพฤติกรรมของวัตถุขึ้นอยู่กับสถานะและต้องเปลี่ยนแปลงขณะรันไทม์ ที่มา original.ru
- เมื่อรหัสการดำเนินการมีคำสั่งแบบมีเงื่อนไขที่ประกอบด้วยหลายสาขาซึ่งการเลือกสาขาขึ้นอยู่กับสถานะ โดยทั่วไปในกรณีนี้ รัฐจะแสดงด้วยค่าคงที่ที่ระบุ บ่อยครั้งที่โครงสร้างคำสั่งแบบมีเงื่อนไขเดียวกันถูกทำซ้ำในหลาย ๆ การดำเนินการ รูปแบบสถานะแนะนำให้วางแต่ละสาขาในคลาสที่แยกจากกัน สิ่งนี้ทำให้คุณสามารถปฏิบัติต่อสถานะของออบเจ็กต์ในฐานะออบเจ็กต์อิสระที่สามารถเปลี่ยนแปลงได้โดยอิสระจากสิ่งอื่น แหล่งที่มาของไซต์ ไซต์ดั้งเดิม
สารละลาย
เว็บไซต์ เว็บไซต์ แหล่งที่มาดั้งเดิมแหล่งที่มา.ru
ผู้เข้าร่วมรูปแบบรัฐ
แหล่งที่มา.ru- บริบท(TCPConnection) - บริบท
กำหนดอินเทอร์เฟซเดียวสำหรับไคลเอ็นต์
เก็บอินสแตนซ์ของคลาสย่อย รัฐคอนกรีตซึ่งกำหนดสถานะปัจจุบัน โค้ดแล็บ - สถานะ(TCPSate) - สถานะ
กำหนดอินเทอร์เฟซสำหรับการห่อหุ้มพฤติกรรมที่เกี่ยวข้องกับสถานะบริบทเฉพาะ แหล่งที่มาของไซต์ ต้นฉบับของไซต์ - คลาสย่อย รัฐคอนกรีต(TCPEstablished, TCPListen, TCPClosed) - สถานะเฉพาะ
แต่ละคลาสย่อยใช้พฤติกรรมที่เกี่ยวข้องกับสถานะบริบทบางอย่าง บริบท. ไซต์ ไซต์ต้นทางดั้งเดิม
โครงการใช้รูปแบบของรัฐ
ระดับ บริบทมอบหมายคำขอไปยังวัตถุปัจจุบัน รัฐคอนกรีต. ไซต์ ไซต์ต้นทางดั้งเดิมบริบทสามารถส่งผ่านตัวเองเป็นข้อโต้แย้งไปยังวัตถุได้ สถานะซึ่งจะประมวลผลคำขอ สิ่งนี้ทำให้วัตถุสถานะ ( รัฐคอนกรีต) เข้าถึงบริบทหากจำเป็น โค้ดแล็บ
บริบท- นี่คืออินเทอร์เฟซหลักสำหรับลูกค้า ลูกค้าสามารถกำหนดค่าบริบทด้วยวัตถุสถานะ สถานะ(อย่างแม่นยำมากขึ้น รัฐคอนกรีต). เมื่อกำหนดค่าบริบทแล้ว ไคลเอนต์ไม่จำเป็นต้องสื่อสารโดยตรงกับออบเจ็กต์สถานะอีกต่อไป (ผ่านอินเทอร์เฟซทั่วไปเท่านั้น สถานะ). แหล่งที่มา.ru
ในกรณีนี้เช่นกัน บริบทหรือคลาสย่อยเอง รัฐคอนกรีตสามารถตัดสินใจได้ว่าการเปลี่ยนแปลงสถานะจะเกิดขึ้นภายใต้เงื่อนไขใดและในลำดับใด เว็บไซต์ แหล่งที่มาของเว็บไซต์ ต้นฉบับ
คำถามเกี่ยวกับการดำเนินการตามรูปแบบของรัฐ
คำถามเกี่ยวกับการดำเนินการตามรูปแบบของรัฐ: ที่มา original.ruอะไรเป็นตัวกำหนดการเปลี่ยนแปลงระหว่างรัฐ
ที่มา original.ru
รูปแบบของรัฐไม่ได้พูดอะไรเกี่ยวกับผู้เข้าร่วมที่กำหนดเงื่อนไข (เกณฑ์) สำหรับการเปลี่ยนแปลงระหว่างรัฐ หากเกณฑ์ได้รับการแก้ไขแล้ว ก็สามารถนำไปใช้ในชั้นเรียนได้โดยตรง บริบท. อย่างไรก็ตาม โดยทั่วไปแล้ว แนวทางที่ยืดหยุ่นและถูกต้องมากกว่าคือการอนุญาตให้คลาสย่อยของคลาสเอง สถานะกำหนดสถานะและช่วงเวลาของการเปลี่ยนแปลงครั้งต่อไป เมื่อต้องการทำเช่นนี้ในชั้นเรียน บริบทเราจำเป็นต้องเพิ่มอินเทอร์เฟซที่อนุญาตจากวัตถุ สถานะกำหนดสถานะของมัน
ตรรกะการเปลี่ยนแปลงแบบกระจายอำนาจนี้ง่ายต่อการแก้ไขและขยาย - คุณเพียงแค่ต้องกำหนดคลาสย่อยใหม่ สถานะ. ข้อเสียของการกระจายอำนาจก็คือแต่ละคลาสย่อย สถานะต้อง "รู้" เกี่ยวกับคลาสย่อยอย่างน้อยหนึ่งคลาสของสถานะอื่น (ซึ่งเขาสามารถเปลี่ยนสถานะปัจจุบันได้จริง) ซึ่งแนะนำการพึ่งพาการใช้งานระหว่างคลาสย่อย แหล่งที่มาของไซต์ ต้นฉบับของไซต์ทางเลือกแบบตาราง
ไซต์ ไซต์ต้นทางดั้งเดิม
มีอีกวิธีหนึ่งในการจัดโครงสร้างโค้ดที่ขับเคลื่อนโดยรัฐ นี่คือหลักการของเครื่องจักรสถานะจำกัด ใช้ตารางเพื่อแมปอินพุตกับการเปลี่ยนสถานะ ด้วยความช่วยเหลือนี้ คุณสามารถกำหนดสถานะที่คุณต้องการไปเมื่อข้อมูลอินพุตบางอย่างมาถึง โดยพื้นฐานแล้ว เรากำลังแทนที่โค้ดแบบมีเงื่อนไขด้วยการค้นหาตาราง
ข้อได้เปรียบหลักของเครื่องคือความสม่ำเสมอ: หากต้องการเปลี่ยนเกณฑ์การเปลี่ยนแปลง ก็เพียงพอที่จะแก้ไขเฉพาะข้อมูล ไม่ใช่โค้ด แต่ก็มีข้อเสียเช่นกัน:
- การค้นหาตารางมักจะมีประสิทธิภาพน้อยกว่าการเรียกใช้ฟังก์ชัน
- การนำเสนอตรรกะการเปลี่ยนแปลงในรูปแบบตารางที่เหมือนกันทำให้เกณฑ์มีความชัดเจนน้อยลง และดังนั้นจึงยากต่อการเข้าใจ
- โดยปกติแล้ว เป็นการยากที่จะเพิ่มการดำเนินการที่มาพร้อมกับการเปลี่ยนระหว่างรัฐ วิธีการแบบตารางคำนึงถึงสถานะและการเปลี่ยนระหว่างสถานะเหล่านั้น แต่จำเป็นต้องเสริมเพื่อให้สามารถคำนวณตามอำเภอใจกับการเปลี่ยนแปลงแต่ละสถานะได้
ความแตกต่างที่สำคัญระหว่างเครื่องสถานะแบบตารางและสถานะรูปแบบสามารถกำหนดได้ดังนี้: รูปแบบสถานะจำลองพฤติกรรมที่ขึ้นกับสถานะ และวิธีการแบบตารางมุ่งเน้นไปที่การกำหนดการเปลี่ยนระหว่างสถานะ ต้นฉบับ.ruการสร้างและทำลายวัตถุของรัฐ
ในระหว่างกระบวนการพัฒนา คุณมักจะต้องเลือกระหว่าง:
- การสร้างวัตถุสถานะเมื่อจำเป็นและทำลายทันทีหลังการใช้งาน
- สร้างไว้ล่วงหน้าและตลอดไปตัวเลือกแรกจะดีกว่าเมื่อไม่ทราบล่วงหน้าว่าระบบจะตกอยู่ในสถานะใด และบริบทเปลี่ยนแปลงสถานะค่อนข้างน้อย ในเวลาเดียวกัน เราไม่ได้สร้างออบเจ็กต์ที่จะไม่มีวันถูกใช้ ซึ่งเป็นสิ่งสำคัญหากข้อมูลจำนวนมากถูกเก็บไว้ในออบเจ็กต์สถานะ เมื่อการเปลี่ยนแปลงสถานะเกิดขึ้นบ่อยครั้ง ดังนั้นคุณจึงไม่ต้องการทำลายอ็อบเจ็กต์ที่เป็นตัวแทน (เนื่องจากอาจจำเป็นต้องใช้อีกครั้งในเร็วๆ นี้) คุณควรใช้แนวทางที่สอง เวลาในการสร้างวัตถุจะใช้เวลาเพียงครั้งเดียวในช่วงเริ่มต้นและไม่ได้ใช้เวลาในการทำลายเลย จริงอยู่ วิธีการนี้อาจกลายเป็นเรื่องไม่สะดวก เนื่องจากบริบทจะต้องจัดเก็บการอ้างอิงถึงสถานะทั้งหมดซึ่งระบบอาจเข้าข่ายในทางทฤษฎีได้ ที่มา original.ru
source.ru ต้นฉบับการใช้การเปลี่ยนแปลงแบบไดนามิก
.ru ต้นฉบับ
เป็นไปได้ที่จะเปลี่ยนแปลงพฤติกรรมตามความต้องการโดยการเปลี่ยนคลาสของอ็อบเจ็กต์ขณะรันไทม์ แต่ภาษาเชิงวัตถุส่วนใหญ่ไม่รองรับสิ่งนี้ ข้อยกเว้นคือ Perl, JavaScript และภาษาที่ใช้สคริปต์เอ็นจิ้นอื่นๆ ซึ่งมีกลไกดังกล่าวและรองรับ Pattern State โดยตรง ซึ่งช่วยให้ออบเจ็กต์ปรับเปลี่ยนพฤติกรรมได้โดยการเปลี่ยนรหัสชั้นเรียน แหล่งที่มา.ru
ผลลัพธ์
ผลลัพธ์การใช้งาน สถานะรูปแบบ: ที่มาของ original.ru- ปรับพฤติกรรมขึ้นอยู่กับรัฐ
และแบ่งออกเป็นส่วนที่สอดคล้องกับรัฐ รูปแบบสถานะทำให้พฤติกรรมทั้งหมดที่เกี่ยวข้องกับสถานะใดสถานะหนึ่งเป็นออบเจ็กต์ที่แยกจากกัน เนื่องจากโค้ดที่ขึ้นอยู่กับสถานะมีอยู่ในคลาสย่อยคลาสใดคลาสหนึ่ง สถานะจากนั้นคุณสามารถเพิ่มสถานะและการเปลี่ยนภาพใหม่ได้ง่ายๆ โดยการวางคลาสย่อยใหม่
เราสามารถใช้สมาชิกข้อมูลเพื่อกำหนดสถานะภายใน จากนั้นจึงดำเนินการของออบเจ็กต์แทน บริบทจะตรวจสอบข้อมูลนี้ แต่ในกรณีนี้ คำสั่งแบบมีเงื่อนไขหรือคำสั่งสาขาที่คล้ายกันจะกระจัดกระจายไปทั่วโค้ดของคลาส บริบท. อย่างไรก็ตาม การเพิ่มสถานะใหม่จะต้องมีการเปลี่ยนแปลงการดำเนินการหลายอย่าง ซึ่งจะทำให้การบำรุงรักษาทำได้ยาก รูปแบบสถานะช่วยแก้ปัญหานี้ แต่ยังสร้างอีกรูปแบบหนึ่ง เนื่องจากพฤติกรรมสำหรับสถานะที่แตกต่างกันจบลงด้วยการกระจายไปยังคลาสย่อยหลายคลาส สถานะ. สิ่งนี้จะเพิ่มจำนวนคลาส แน่นอนว่าคลาสหนึ่งจะมีขนาดกะทัดรัดกว่า แต่ถ้ามีหลายรัฐ การแจกแจงดังกล่าวจะมีประสิทธิภาพมากกว่า เนื่องจากไม่เช่นนั้นจะต้องจัดการกับคำสั่งที่มีเงื่อนไขที่ยุ่งยาก
การมีเงื่อนไขที่ยุ่งยากเป็นสิ่งที่ไม่พึงประสงค์ เช่นเดียวกับการมีขั้นตอนที่ยาวนาน พวกมันมีขนาดใหญ่เกินไป ซึ่งเป็นเหตุผลว่าทำไมการแก้ไขและขยายโค้ดจึงกลายเป็นปัญหา รูปแบบสถานะเสนอวิธีที่ดีกว่าในการจัดโครงสร้างโค้ดที่ขึ้นกับสถานะ ลอจิกที่อธิบายการเปลี่ยนสถานะไม่ได้รวมอยู่ในคำสั่งแบบเสาหินอีกต่อไป ถ้าหรือ สวิตช์แต่กระจายระหว่างคลาสย่อย สถานะ. ด้วยการห่อหุ้มแต่ละการเปลี่ยนแปลงและการดำเนินการไว้ในคลาส สถานะจะกลายเป็นวัตถุที่เต็มเปี่ยม สิ่งนี้ช่วยปรับปรุงโครงสร้างของโค้ดและทำให้วัตถุประสงค์ของมันชัดเจนยิ่งขึ้น ต้นฉบับ.ru - ทำให้การเปลี่ยนผ่านระหว่างรัฐชัดเจน
หากวัตถุกำหนดสถานะปัจจุบันในแง่ของข้อมูลภายในเท่านั้น การเปลี่ยนระหว่างสถานะจะไม่มีการแสดงที่ชัดเจน ปรากฏเป็นการมอบหมายให้กับตัวแปรบางตัวเท่านั้น การแนะนำออบเจ็กต์ที่แยกจากกันสำหรับสถานะที่แตกต่างกันทำให้การเปลี่ยนมีความชัดเจนมากขึ้น นอกจากนี้วัตถุ สถานะสามารถปกป้องบริบทได้ บริบทจากความไม่ตรงกันของตัวแปรภายใน เนื่องจากการเปลี่ยนจากมุมมองของบริบทเป็นการกระทำแบบอะตอมมิก หากต้องการทำการเปลี่ยนแปลง คุณต้องเปลี่ยนค่าของตัวแปรเพียงตัวเดียวเท่านั้น (ตัวแปร object สถานะในชั้นเรียน บริบท) แทนที่จะเป็นหลายรายการ แหล่งที่มา.ru - สามารถแชร์วัตถุสถานะได้
หากอยู่ในวัตถุสถานะ สถานะไม่มีตัวแปรอินสแตนซ์ หมายความว่าสถานะที่แสดงนั้นจะถูกเข้ารหัสตามประเภทเท่านั้น จากนั้นบริบทที่แตกต่างกันก็สามารถแชร์ออบเจ็กต์เดียวกันได้ สถานะ. เมื่อรัฐถูกแยกออกจากกันในลักษณะนี้ โดยพื้นฐานแล้วรัฐเหล่านั้นจะเป็นผู้ฉวยโอกาส (ดูรูปแบบลัทธิฉวยโอกาส) ซึ่งไม่มีสถานะภายใน มีเพียงพฤติกรรมเท่านั้น แหล่งที่มาของไซต์ ไซต์ดั้งเดิม
ตัวอย่าง
ลองดูที่การนำตัวอย่างไปใช้จากส่วน "" เช่น สร้างสถาปัตยกรรมการเชื่อมต่อ 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 แยกสิ่งที่เป็นนามธรรมจากการนำไปปฏิบัติเพื่อให้สามารถเปลี่ยนแปลงได้อย่างอิสระจากกัน
- การดำเนินการตามรูปแบบของรัฐจะขึ้นอยู่กับรูปแบบยุทธศาสตร์ ความแตกต่างอยู่ในจุดประสงค์ของพวกเขา
การดำเนินการตามรูปแบบของรัฐ
ลองพิจารณาตัวอย่างของเครื่องสถานะจำกัดที่มีสถานะที่เป็นไปได้สองสถานะและสองเหตุการณ์
#รวม
15.02.2016
21:30
รูปแบบสถานะมีไว้สำหรับการออกแบบคลาสที่มีสถานะลอจิคัลอิสระหลายตัว มาดูตัวอย่างกันดีกว่า
สมมติว่าเรากำลังพัฒนาคลาสควบคุมเว็บแคม กล้องสามารถอยู่ในสามสถานะ:
- ไม่ได้เตรียมใช้งาน เรียกมันว่า NotConnectedState ;
- เริ่มต้นและพร้อมใช้งานแล้ว แต่ยังไม่มีการบันทึกเฟรมใดๆ ปล่อยให้มันเป็น ReadyState ;
- โหมดจับภาพเฟรมที่ใช้งานอยู่ มาแสดง ActiveState กัน
เนื่องจากเรากำลังทำงานกับรูปแบบของรัฐ วิธีที่ดีที่สุดคือเริ่มต้นด้วยภาพแผนภาพรัฐ:
ตอนนี้เรามาเปลี่ยนไดอะแกรมนี้เป็นโค้ดกัน เพื่อไม่ให้การใช้งานยุ่งยาก เราจึงละเว้นโค้ดสำหรับการทำงานกับกล้องเว็บ หากจำเป็น คุณสามารถเพิ่มฟังก์ชันไลบรารีที่เหมาะสมได้ด้วยตัวเอง
ฉันจะให้ข้อมูลรายการทั้งหมดพร้อมความคิดเห็นเพียงเล็กน้อยทันที ต่อไปเราจะหารือรายละเอียดที่สำคัญของการดำเนินการนี้โดยละเอียดยิ่งขึ้น
#รวม
ฉันดึงความสนใจของคุณไปที่มาโคร 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 ระดับหลัก ด้วยเหตุนี้ เมื่อเขียนแต่ละรัฐ เราก็สามารถมุ่งความสนใจไปที่งานแคบๆ ได้ และนี่คือวิธีที่ดีที่สุดในการเขียนโค้ดที่ชัดเจน เข้าใจได้ และเชื่อถือได้