Command חלק ב’ Undo/Redo

23 בSeptember 2012

4 תגובות

Command חלק ב – Undo / Redo

אם הבנו את פרק א, בואו ננסה להציג משהו מורכב יותר. (ושימושי)

כאמור הזכרתי בחלק א  ששימוש בcommand במקום פונקציות רגילות
נותן לנו את האפשרות של
undo /redo .

בניתם אפליקציה? מזל טוב.. תחשבו אם לא כדאי להוסיף יכולת כזאת לפני
שאתם מעלים ל
appStore..

בינתיים נבין אז איך זה עובד.

  אני אדגים את הרעיון על
רשימת מוצרים שאני יכול להוסיף או לגרוע ממנה

אבל הכי חשוב אני יכול להתחרט ולמחזר את הפעולות שלי, ז”א שאם
מחקתי איבר מהרשימה יהיה לי את הכוח להחזיר אותו..

יותר מזה אני יכול להתחרט על המחזור ולמחזר אות המחזור בעצמו..
ז”א כן למחוק את אותו המוצר שנמחק והתחרטתי לגביו..

אוקיי
אחרי שבלבלנו אתכם כהוגן (: עם משחקי מילים הבה נקודד מעט.

 

חשוב שנכיר את מבנה הנתונים Stack. משום שנשתמש בו לצורכי
הביצוע.

 

 בעיקרון נשתמש בשתי הפונקציות המרכזיות שלו:

Push

Pop

שזה אומר תכניס לערימה אחד מלמעלה או תעיף
מהערימה את האיבר העליון.

 

א.     
נגדיר
חוזה עבור היכולת של
Undo\Redo שייראה בערך כך:

 

    //contract
for commands

    public interface IUndoRedoCommand

    {

        void undo();

        void redo();

   
}

ב.     
נגדיר קלאס שמנהל את הרעיון של Undo\Redo ויחזיק 2 מחסניות אחת עבור כל אופרציה של החוזה: חשוב לשים לב שאין
קשר בין החוזה לקלאס הזה , כאן אנחנו רק מנהלים את המחסניות

ששומרות
סטייט.

השימוש
הפנימי כאן אומר שכשאני נדרש לבצע
UNDO  על משהו אני פונה למחסנית
המרכזית,

בוחר את
האיבר הראשון מלמעלה , מעיף אותו ונגמר..

אלא שכדי
לשמר את היכולת לעשות
REDO  אני מעיף אותו למחסנית
נוספת ששומרת

עליו
לזמן מסוים ולא מעלים אותו לגמרי.

//represent
backup system for undo/redo mechanism

    public class UnDoRedoManager

    {

        public Stack<IUndoRedoCommand> UndoCommands { set; get; }

        public Stack<IUndoRedoCommand> RedoCommands { set; get; }

        public UnDoRedoManager()

        {

            UndoCommands = new Stack<IUndoRedoCommand>();

            RedoCommands = new Stack<IUndoRedoCommand>();

        }

 

        public void UndoAction()

        {

            if (UndoCommands.Count != 0)

            {

                IUndoRedoCommand command = UndoCommands.Pop();

                command.undo();

                RedoCommands.Push(command);

            }

        }

        public void RedoAction()

        {

            if (RedoCommands.Count != 0)

            {

                IUndoRedoCommand command = RedoCommands.Pop();

                command.redo();

                UndoCommands.Push(command);

            }

        }

 

   
}

 

      }                                                             
                  

 

ג.       נגדיר את המוצר :

// the model

    public class Product

    {

        public Guid Id { get; set; }

        public string Name { get; set; }

    }                                 

 

 

ד.     
עכשיו
ניגש לבנות את שני ה
commands המרכזיים פה שמן הסתם
יהיו הוספה והסרה.

כל אחד יממש בדרכו את החוזה שהגדרנו בתחילת
הפוסט , המימוש כאן הוא ממש פשוט

אני מקבל מהאפליקציה את המוצר שאותו נרצה
להוסיף או להסיר, מקבל גם את הרשימה הנוכחית,

(שימו לב שזה מתבצע בקונסטרקטור , כך שאין
מקום לפספוסים) בנוסף אני מחזיק

בכל command כזה שלש פונקציות :

מימוש של
הפעולה המרכזית – במילים אחרות ביצוע הפעולה שאותה אנו עוטפים

  לדוגמא אצלנו addcommand ייגש פיזית אל הרשימה
ויוסיף..

             ובנוסף מימוש UNDO +  REDO  מה
שנובע מהחוזה .

אני קורא לפעולה המרכזית בקונסטרקטור משום שזאת מהות הCommand , ואילו בעת קריאה לUNDO

אני מפעיל את הפעולה הנגדית ( UNDO על הוספה
, מפעיל את הסרה על אותו מוצר, וכן להיפך)

//implementation
of Add by command

    public class AddCommand : IUndoRedoCommand

    {

        public Product productToAdd { get; set; }

        public List<Product> allProducts { get; set; }

 

        public AddCommand(Product product, List<Product> Products)

        {

            productToAdd = product;

            allProducts = Products;

            insert();

        }

 

        public void undo()

        {

            RemovCommand remove = new RemovCommand(productToAdd, allProducts);

        }

 

        public void redo()

        {

            insert();

        }

 

        private void insert()

        {

            allProducts.Add(productToAdd);

        }

    }

 

    //implementation
of remove by command

    public class RemovCommand : IUndoRedoCommand

    {

        public Product productToRemove { get; set; }

        public List<Product> allProducts { get; set; }

 

 

        public RemovCommand(Product product, List<Product> Products)

        {

            productToRemove = product;

            allProducts = Products;

            delete();

        }

        public void undo()

        {

            AddCommand add = new AddCommand(productToRemove, allProducts);

        }

 

        public void redo()

        {

            delete();

        }

 

        private void delete()

        {

           
allProducts.Remove(productToRemove);

        }

 

    }

 

ה.     
מה שנותר
להגדיר זה את הקלאס שמייצג את כל המוצרים ומשתמש בתשתית שהצגנו כאן.

לקלאס הזה ישנה רשימת מוצרים ,

 אובייקט מסוג UnDoRedoManager שכאמור מתפעל את המנגנון,

וחמש
פעולות אפשריות , הוספה , הסרה,
Undo  , Redo , והדפסת כל
המוצרים(לצורכי
debug).

שימו לב
שעיקר הקסם מתבצע שבעת הכנסה לרשימה לדוגמא, במקום לקרוא ל
add סטנדרטי אני
יוצר אובייקט
addCommand וגם מעדכן את מנגנון  ה UnDoRedoManager.

 

 //represent all products with user functions

    public class AllProducts

    {

        UnDoRedoManager UndoRedoCenter;

        public List<Product> ProductsList { get; set; }

        public AllProducts()

        {

            UndoRedoCenter = new UnDoRedoManager();

            ProductsList = new List<Product>();

        }

        public void AddProduct(Product p)

        {

            AddCommand add = new AddCommand(p, ProductsList);

           
UndoRedoCenter.UndoCommands.Push(add);

        }

 

        public void RemoveProduct(Product p)

        {

            RemovCommand remove = new RemovCommand(p, ProductsList);

           
UndoRedoCenter.UndoCommands.Push(remove);

        }

 

        public void UndoLastAction()

        {

            UndoRedoCenter.UndoAction();

        }

 

        public void RedoLastAction()

        {

            UndoRedoCenter.RedoAction();

        }

 

        public void printDetails()

        {

            if (ProductsList.Count > 0)

            {

                foreach (var item in ProductsList)

                {

                    Console.WriteLine(item.Name);

                }

                Console.WriteLine(“>>>>>
now in the store..”
);

            }

        }

    }

 

ו.       
זהו
סיימנו, ( נוסיף הרצה  לבדיקה):

   

    class Program

    {

        static void Main(string[] args)

        {

            Product p1 = new Product() { Name = “cocca”, Id = Guid.NewGuid() };

            Product p2 = new Product() { Name = “pepsi”, Id = Guid.NewGuid() };

            Product p3 = new Product() { Name = “rc”, Id = Guid.NewGuid() };

 

 

            AllProducts store = new AllProducts();

 

            store.AddProduct(p1);

            store.AddProduct(p2);

            store.printDetails();

            store.UndoLastAction();

            store.printDetails();

            store.RedoLastAction();

            store.printDetails();

        }

 

    }
      ז.    נקודה אחרונה: חשוב לתכנן מראש כמה פעולות כאלה מותרות במערכת שלנו (לתת קיבולת למחסנית לדוגמא)
                 כי אנחנו נכנסים לעליות זיכרון גבוהות על כל command כזה שמוחזק במחסנית .
הוסף תגובה
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

4 תגובות

  1. יוני23 בSeptember 2012 ב 12:56

    אחלה פוסט. תודה!

    Reply
  2. dima23 בSeptember 2012 ב 12:57

    why you didn’t show implementation on type of as a generics

    Reply
  3. איאהב17 בNovember 2012 ב 11:49

    Good. ! it is works for me !!

    Reply
  4. מיטל 11 בJanuary 2013 ב 7:45

    תודה . זה ממש שימושי!

    Reply