รูปแบบการออกแบบสำหรับ Undo Engine


117

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

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

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

เราจะดำเนินการได้อย่างไร?


หากฉันเพิ่มความคิดเห็น "Undo Algorthim" จะทำให้ฉันสามารถค้นหา "เลิกทำอัลกอริทึม" และพบสิ่งนี้หรือไม่ นั่นคือสิ่งที่ฉันค้นหาและพบว่ามีบางอย่างปิดเป็นรายการที่ซ้ำกัน
Peter Turner

ฉันยังต้องการพัฒนาเลิกทำ / ทำซ้ำในแอปพลิเคชันที่เรากำลังพัฒนาเราใช้กรอบงาน QT4 และจำเป็นต้องมีการดำเนินการเลิกทำ / ทำซ้ำที่ซับซ้อนมากมายฉันสงสัยว่าคุณประสบความสำเร็จในการใช้ Command-Pattern หรือไม่?
Ashika Umanga Umagiliya

2
@umanga: มันได้ผล แต่มันไม่ง่ายเลย ส่วนที่ยากที่สุดคือการติดตามข้อมูลอ้างอิง ตัวอย่างเช่นเมื่ออ็อบเจ็กต์ Frame ถูกลบอ็อบเจ็กต์ย่อยของอ็อบเจ็กต์: โหนด, โหลดที่ทำงานบนอ็อบเจ็กต์และการกำหนดผู้ใช้อื่น ๆ จำนวนมากจำเป็นต้องเก็บไว้เพื่อใส่เข้าไปใหม่เมื่อเลิกทำ แต่บางส่วนของวัตถุลูกเหล่านี้ถูกแชร์กับวัตถุอื่นและตรรกะการเลิกทำ / ทำซ้ำก็ค่อนข้างซับซ้อน ถ้าโมเดลไม่ใหญ่ขนาดนั้นฉันจะคงแนวทางของที่ระลึกไว้ มันง่ายกว่ามากในการนำไปใช้
Ozgur Ozcitak

นี่เป็นปัญหาที่น่าสนุกในการแก้ไขลองคิดดูว่าซอร์สโค้ด repos ทำอย่างไรเช่น svn (พวกเขารักษาความแตกต่างระหว่างคอมมิต)
Alex

คำตอบ:


88

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


4
นี่คือวิธีการทำงานของเครื่องมือเลิกทำใน Cocoa, NSUndoManager
amrox

33

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

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

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

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

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


1
หากคุณใช้ฐานข้อมูล (เช่น sqlite) เป็นรูปแบบไฟล์ของคุณสิ่งนี้อาจเป็นไปโดยอัตโนมัติ
Martin Beckett

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

คุณช่วยอธิบายแนวคิด vs พอยน์เตอร์ของ id นี้เพิ่มเติมได้ไหม แน่นอนว่าที่อยู่ตัวชี้ / หน่วยความจำทำงานได้เช่นเดียวกับ id หรือไม่?
paulm

@paulm: โดยพื้นฐานแล้วข้อมูลจริงจะถูกจัดทำดัชนีโดย (id, version) พอยน์เตอร์หมายถึงอ็อบเจ็กต์เวอร์ชันใดเวอร์ชันหนึ่ง แต่คุณต้องการอ้างถึงสถานะปัจจุบันของอ็อบเจ็กต์ไม่ว่าจะเป็นอะไรก็ตามดังนั้นคุณจึงต้องการระบุโดย id ไม่ใช่ตาม (id เวอร์ชัน) คุณสามารถปรับโครงสร้างใหม่เพื่อจัดเก็บตัวชี้ไปที่ตาราง (version => data) และเลือกข้อมูลล่าสุดในแต่ละครั้ง แต่อาจเป็นอันตรายต่อท้องถิ่นเมื่อคุณมีข้อมูลที่ยังคงอยู่ความยุ่งเหยิงจะกังวลเล็กน้อยและทำให้ยากต่อการ ทำแบบสอบถามทั่วไปบางประเภทดังนั้นจึงไม่ใช่วิธีที่ปกติจะทำ
Chris Morgan

17

หากคุณกำลังพูดถึง GoF รูปแบบMementoจะกล่าวถึงการเลิกทำโดยเฉพาะ


7
ไม่จริงสิ่งนี้กล่าวถึงแนวทางเริ่มต้นของเขา เขากำลังขอแนวทางอื่น การเริ่มต้นกำลังจัดเก็บสถานะเต็มสำหรับแต่ละขั้นตอนในขณะที่ส่วนหลังกำลังจัดเก็บเฉพาะ "diffs"
Andrei Rînea

15

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

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

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

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


1
ฉันไม่เคยคิดว่าจะpasteเป็นcut^ -1
Lenar Hoyt

8

คุณอาจต้องการอ้างถึงรหัส Paint.NETสำหรับการเลิกทำ - พวกเขามีระบบเลิกทำที่ดีมาก อาจจะง่ายกว่าที่คุณต้องการเล็กน้อย แต่อาจให้แนวคิดและแนวทางแก่คุณได้

อดัม


4
ที่จริงรหัส Paint.NET ไม่เป็นมี แต่คุณจะได้รับคดเคี้ยวcode.google.com/p/paint-mono
อิกอร์ Brejc

7

นี่อาจเป็นกรณีที่ใช้CSLAได้ ได้รับการออกแบบมาเพื่อให้การสนับสนุนการเลิกทำที่ซับซ้อนสำหรับวัตถุในแอปพลิเคชัน Windows Forms


6

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

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

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

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

ระบบเลิกทำจำนวนมากอยู่ในหน่วยความจำเท่านั้น แต่คุณสามารถคงไว้ซึ่งการเลิกทำสแต็กได้หากต้องการฉันเดา


5

เพิ่งอ่านเกี่ยวกับรูปแบบคำสั่งในหนังสือการพัฒนา Agile ของฉัน - นั่นอาจเป็นไปได้?

คุณสามารถให้ทุกคำสั่งใช้อินเตอร์เฟสคำสั่ง (ซึ่งมีเมธอด Execute ()) หากคุณต้องการเลิกทำคุณสามารถเพิ่มวิธีการเลิกทำ

ข้อมูลเพิ่มเติมที่นี่


4

ฉันอยู่กับMendelt Siebengaเกี่ยวกับความจริงที่ว่าคุณควรใช้รูปแบบคำสั่ง รูปแบบที่คุณใช้คือรูปแบบ Memento ซึ่งสามารถและจะเสียเวลาไปมาก

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

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


3

โครงการ Codeplex :

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


2

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


คุณจะใส่อะไรใน deque?

ในกรณีของฉันฉันระบุสถานะปัจจุบันของการดำเนินการที่ฉันต้องการฟังก์ชันเลิกทำ / ทำซ้ำ ด้วยการมีสอง deques (เลิกทำ / ทำซ้ำ) ฉันจะเลิกทำในคิวเลิกทำ (ป๊อปไอเท็มแรก) และใส่ลงในการทำคิวซ้ำ หากจำนวนรายการใน dequeues เกินขนาดที่ต้องการฉันจะแสดงรายการของส่วนท้าย
Patrik Svensson

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

หรือคุณสามารถจัดเก็บคู่ของการปิดที่แสดงถึงการทำงานปกติและเลิกทำ
Xwtek

2

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

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



1

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

มีข้อบกพร่องมากมายเนื่องจากพอยน์เตอร์ (C ++) ไปยังอ็อบเจ็กต์ที่ไม่เคยแก้ไขเมื่อคุณดำเนินการยกเลิกการทำซ้ำแบบแปลก ๆ (สถานที่เหล่านั้นไม่ได้รับการอัปเดตเพื่อ "ตัวระบุ" ที่ปลอดภัยกว่า) บักแถวนี้บ่อย ... อืมม ... น่าสนใจ

การดำเนินการบางอย่างอาจเป็นกรณีพิเศษสำหรับการใช้ความเร็ว / ทรัพยากรเช่นการปรับขนาดสิ่งของการเคลื่อนย้ายสิ่งต่างๆ

การเลือกหลายรายการทำให้เกิดภาวะแทรกซ้อนที่น่าสนใจเช่นกัน โชคดีที่เรามีแนวคิดการจัดกลุ่มในโค้ดแล้ว ความคิดเห็นของ Kristopher Johnson เกี่ยวกับรายการย่อยนั้นค่อนข้างใกล้เคียงกับสิ่งที่เราทำ


สิ่งนี้ฟังดูใช้ไม่ได้มากขึ้นเมื่อขนาดของโมเดลของคุณเติบโตขึ้น
Warren P

อย่างไหนล่ะ, แบบไหนล่ะ? วิธีนี้ยังคงใช้งานได้โดยไม่มีการเปลี่ยนแปลงเมื่อมีการเพิ่ม "สิ่งของ" ใหม่ให้กับวัตถุ ประสิทธิภาพอาจเป็นปัญหาเนื่องจากรูปแบบที่ต่อเนื่องกันของวัตถุมีขนาดใหญ่ขึ้น - แต่นี่ไม่ใช่ปัญหาสำคัญ ระบบได้รับการพัฒนาอย่างต่อเนื่องเป็นเวลา 20 ปีขึ้นไปและมีผู้ใช้งานกว่า 1,000 คน
Aardvark

1

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


1

คุณสามารถลองใช้รูปแบบเลิกทำ / ทำซ้ำสำเร็จรูปใน PostSharp https://www.postsharp.net/model/undo-redo

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

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


0

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



0

คุณสามารถทำให้ไอเดียเริ่มต้นของคุณมีประสิทธิภาพ

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


0

ฉันพบว่ารูปแบบคำสั่งมีประโยชน์มากที่นี่ แทนที่จะใช้คำสั่งย้อนกลับหลายคำสั่งฉันใช้การย้อนกลับด้วยการดำเนินการที่ล่าช้าในอินสแตนซ์ที่สองของ API ของฉัน

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

ดูตัวอย่างได้ที่นี่: https://github.com/thilo20/Undo/


-1

ฉันไม่รู้ว่านี่จะเป็นประโยชน์กับคุณหรือเปล่า แต่เมื่อฉันต้องทำสิ่งที่คล้ายกันในโปรเจ็กต์หนึ่งของฉันฉันก็ดาวน์โหลด UndoEngine จากhttp://www.undomadeeasy.comซึ่งเป็นเอ็นจิ้นที่ยอดเยี่ยม และฉันก็ไม่ได้สนใจมากเกินไปเกี่ยวกับสิ่งที่อยู่ใต้ฝากระโปรง - มันใช้งานได้จริง


กรุณาโพสต์ความคิดเห็นของคุณเป็นคำตอบหากคุณมั่นใจที่จะให้คำตอบเท่านั้น! ไม่งั้นชอบโพสไว้เป็น comment ใต้คำถาม! (หากไม่อนุญาตให้ทำตอนนี้โปรดรอจนกว่าคุณจะได้รับชื่อเสียงที่ดี)
InfantPro'Aravind '11

-1

ในความคิดของฉัน UNDO / REDO สามารถใช้งานได้ 2 วิธีในวงกว้าง 1. ระดับคำสั่ง (เรียกว่าระดับคำสั่ง Undo / Redo) 2. ระดับเอกสาร (เรียกว่า Global Undo / Redo)

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

ข้อ จำกัด : เมื่อขอบเขตของคำสั่งหมดการเลิกทำ / ทำซ้ำจะเป็นไปไม่ได้ซึ่งจะนำไปสู่ระดับเอกสาร (ทั่วโลก) เลิกทำ / ทำซ้ำ

ฉันเดาว่ากรณีของคุณจะเข้ากับการเลิกทำ / ทำซ้ำทั่วโลกเนื่องจากเหมาะสำหรับรุ่นที่มีพื้นที่หน่วยความจำจำนวนมาก นอกจากนี้ยังเหมาะสำหรับการเลือกเลิกทำ / ทำซ้ำด้วย มีสองประเภทดั้งเดิม

  1. หน่วยความจำทั้งหมดเลิกทำ / ทำซ้ำ
  2. ระดับวัตถุเลิกทำซ้ำ

ใน "ยกเลิก / ทำซ้ำหน่วยความจำทั้งหมด" หน่วยความจำทั้งหมดจะถือว่าเป็นข้อมูลที่เชื่อมต่อกัน (เช่นต้นไม้หรือรายการหรือกราฟ) และหน่วยความจำได้รับการจัดการโดยแอปพลิเคชันแทนที่จะเป็นระบบปฏิบัติการ ดังนั้นตัวดำเนินการใหม่และลบหากใน C ++ มีมากเกินไปเพื่อให้มีโครงสร้างที่เฉพาะเจาะจงมากขึ้นเพื่อใช้การดำเนินการอย่างมีประสิทธิภาพเช่น a. ถ้าโหนดใด ๆ ถูกแก้ไข b. การเก็บและล้างข้อมูลเป็นต้นวิธีการทำงานโดยทั่วไปคือการคัดลอกหน่วยความจำทั้งหมด (สมมติว่าการจัดสรรหน่วยความจำได้รับการปรับให้เหมาะสมแล้วและจัดการโดยแอปพลิเคชันโดยใช้อัลกอริทึมขั้นสูง) และจัดเก็บไว้ในกองซ้อน หากมีการร้องขอสำเนาของหน่วยความจำโครงสร้างทรีจะถูกคัดลอกตามความจำเป็นในการคัดลอกแบบตื้นหรือแบบลึก สำเนาแบบลึกถูกสร้างขึ้นเฉพาะสำหรับตัวแปรที่แก้ไข เนื่องจากทุกตัวแปรถูกจัดสรรโดยใช้การจัดสรรแบบกำหนดเอง แอปพลิเคชันมีคำพูดสุดท้ายว่าจะลบเมื่อใดหากจำเป็น สิ่งที่น่าสนใจมากถ้าเราต้องแบ่งพาร์ติชัน Undo / Redo เมื่อมันเกิดขึ้นเราจำเป็นต้องเลือก Undo / Redo ชุดการทำงานที่เลือกโดยโปรแกรม ในกรณีนี้เฉพาะตัวแปรใหม่เหล่านั้นหรือตัวแปรที่ถูกลบหรือตัวแปรที่แก้ไขแล้วเท่านั้นที่จะได้รับแฟล็กเพื่อให้ Undo / Redo ยกเลิก / ทำซ้ำหน่วยความจำเหล่านั้นเท่านั้นสิ่งที่น่าสนใจยิ่งขึ้นหากเราจำเป็นต้องทำการ Undo / Redo บางส่วนภายในออบเจ็กต์ เมื่อเป็นเช่นนี้จึงมีการใช้แนวคิด "รูปแบบผู้เยี่ยมชม" ที่ใหม่กว่า เรียกว่า "Object Level Undo / redo" หรือตัวแปรที่ถูกลบหรือตัวแปรที่แก้ไขจะได้รับแฟล็กเพื่อให้ Undo / Redo เท่านั้นเลิกทำ / ทำซ้ำหน่วยความจำเหล่านั้นสิ่งต่างๆจะน่าสนใจยิ่งขึ้นหากเราจำเป็นต้องทำการ Undo / Redo บางส่วนภายในออบเจ็กต์ เมื่อเป็นเช่นนี้จึงมีการใช้แนวคิด "รูปแบบผู้เยี่ยมชม" ที่ใหม่กว่า เรียกว่า "Object Level Undo / redo" หรือตัวแปรที่ถูกลบหรือตัวแปรที่แก้ไขจะได้รับแฟล็กเพื่อให้ Undo / Redo เท่านั้นเลิกทำ / ทำซ้ำหน่วยความจำเหล่านั้นสิ่งต่างๆจะน่าสนใจยิ่งขึ้นหากเราจำเป็นต้องทำการ Undo / Redo บางส่วนภายในออบเจ็กต์ เมื่อเป็นเช่นนี้จึงมีการใช้แนวคิด "รูปแบบผู้เยี่ยมชม" ที่ใหม่กว่า เรียกว่า "Object Level Undo / redo"

  1. ระดับออบเจ็กต์เลิกทำ / ทำซ้ำ: เมื่อมีการเรียกการแจ้งเตือนให้เลิกทำ / ทำซ้ำทุกออบเจ็กต์จะดำเนินการสตรีมมิ่งซึ่งสตรีมเมอร์จะได้รับข้อมูลเก่า / ข้อมูลใหม่จากอ็อบเจ็กต์ที่ตั้งโปรแกรมไว้ ข้อมูลที่ไม่ถูกรบกวนจะไม่ถูกรบกวน ทุกออบเจ็กต์ได้รับสตรีมเมอร์เป็นอาร์กิวเมนต์และภายในการเรียก UNDo / Redo จะสตรีม / ยกเลิกการสตรีมข้อมูลของวัตถุ

ทั้ง 1 และ 2 อาจมีวิธีการเช่น 1. BeforeUndo () 2. AfterUndo () 3. BeforeRedo () 4. AfterRedo () ต้องเผยแพร่วิธีการเหล่านี้ในคำสั่ง Undo / redo พื้นฐาน (ไม่ใช่คำสั่งตามบริบท) เพื่อให้อ็อบเจ็กต์ทั้งหมดใช้เมธอดเหล่านี้ด้วยเพื่อให้ได้การดำเนินการเฉพาะ

กลยุทธ์ที่ดีคือการสร้างลูกผสมของ 1 และ 2 ความสวยงามก็คือวิธีการเหล่านี้ (1 & 2) เองใช้รูปแบบคำสั่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.