Design Patterns - Memento and Observer

08 juli 2022 om 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we discussed the mediator design pattern, which falls under the category of behavioral design patterns. Today, we will explore two more design patterns from the same category called Memento and Observer.

Table of contents

  1. Introduction to Memento Design Pattern
  2. Implementation of Memento Design Pattern
  3. Introduction to Observer Design Pattern
  4. Implementation of Observer Design Pattern
  5. Conclusion

Introduction to Memento Design Pattern

The Memento design pattern provides an easy option to store the current state of an object and make it usable at a later point in time (in the code) without obstructing the encapsulation rules. It provides multiple options to create checkpoints and retrieve the object’s state based on the requirement. Put simply; the Memento Design Pattern helps in implementing the undo or rollback functionalities in the code.

Implementation of Memento Design Pattern

The Memento design pattern mainly consists of:

  • Originator
  • Memento
  • Caretaker
  • Client
using System;
using System.Collections.Generic;
using System.Linq;

namespace PARTECH_Memento
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Originator originator = new Originator("Initial State.");
            Caretaker caretaker = new Caretaker(originator);

            caretaker.Backup();
            originator.Action();

            caretaker.Backup();
            originator.Action();

            caretaker.Backup();
            originator.Action();

            caretaker.Undo();

            Console.ReadLine();
        }
    }

    public class Originator
    {
        private string state { get; set; }

        public Originator(string state)
        {
            this.state = state;
            Console.WriteLine($"Initial State of Originator - {state}");
        }

        public Memento Save()
        {
            return new Memento(state);
        }

        public void Restore(Memento toRestore)
        {
            state = toRestore.GetState();
            Console.WriteLine($"Restored State - {state}");
        }

        public void Action()
        {
            var random = new Random();
            state = $"State -  { random.Next(1000, 2000) }";
            Console.WriteLine($"Updated State - {state}");
        }
    }

    public class Memento
    {
        private string state { get; set; }
        public DateTime date { get; set; }

        public Memento(string state)
        {
            this.state = state;
            this.date = DateTime.UtcNow;
        }

        public string GetState()
        {
            return state;
        }
    }

    public class Caretaker
    {
        private List<Memento> _mementos = new List<Memento>();

        private Originator _originator;

        public Caretaker(Originator originator)
        {
            _originator = originator;
        }

        public void Backup()
        {
            Console.WriteLine($"Back up done - saving Originator");
            _mementos.Add(_originator.Save());
        }

        public void Undo()
        {
            if(_mementos.Count > 0)
            {
                var undoState = _mementos.Last();
                _mementos.Remove(undoState);
                _originator.Restore(undoState);
            }
        }
    }
}

Here,

Originator is the class where the interaction or modification happens regularly. It has an action method where the content of the class is modified(for simplicity, we are considering the content as a single variable). It also has methods like Save and Restore, where the class creates a new memento object for saving and retrieving. It retrieves the state from the passed variable.

Memento is a class that has a single property, constructor, and a method to get the state details with the object creation. It stores it in a property and retrieves it back once the restore request comes.

Caretaker class is more of a wrapper class for memento class. In this, the memento operations are carried out under the methods of the caretaker class.

The main method is the client class here, where the objects for originator and caretaker are created. The client need not worry about the object creation of the memento class, which happens internally to the caretaker.

On executing the above code, the below output gets printed -

Memento patterns can be implemented in areas where we need to create undo and redo actions. Also, it is the perfect choice for online forms, particularly the ones that span out multiple pages, and each page has a save functionality.

Introduction to Observer Design Pattern

The Observer design pattern provides the ability to send the status of an object to the dependent objects. This is useful in scenarios where one-to-many dependencies between objects are present, particularly in scenarios where the main object is modified, and that state is communicated to dependent objects.

Implementation of Observer Design Pattern

The observer design pattern mainly consists of:

  • Subject
  • Concrete Subject
  • Observer
  • Concrete Observer
using System;
using System.Collections.Generic;

namespace PARTECH_Observer
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var subject = new Subject();
            var observerA = new ConcreteObserverA();
            subject.Attach(observerA);

            var observerB = new ConcreteObserverB();
            subject.Attach(observerB);

            subject.DoAction();
            subject.DoAction();

            subject.Detach(observerA);

            subject.DoAction();

            Console.ReadLine();
        }
    }

    public interface IObserver
    {
        void Update(ISubject subject);
    }
    public interface ISubject
    {
        void Attach(IObserver observer);
        void Detach(IObserver observer);
        void Notify();
    }
    public class ConcreteObserverA: IObserver
    {
        public void Update(ISubject subject)
        {
            Console.WriteLine($"Updated Concrete Observer A for change in subject");
        }
    }

    public class ConcreteObserverB : IObserver
    {
        public void Update(ISubject subject)
        {
            Console.WriteLine($"Updated Concrete Observer B for change in subject");
        }
    }

    public class Subject : ISubject
    {
        private List<IObserver> observers = new List<IObserver>();

        public void Attach(IObserver observer)
        {
            observers.Add(observer);
        }

        public void Detach(IObserver observer)
        {
            observers.Remove(observer);
        }

        public void Notify()
        {
            Console.WriteLine("Subject: Communicating to observers.");

            observers.ForEach(x => x.Update(this));
        }

        public void DoAction()
        {
            Console.WriteLine("Subject is updated.");
            Notify();
        }
    }
}

Here,

Isubject and IObserver are the two interfaces that have the respective method declarations. They need to be implemented by a subject and observer class, respectively.

The subject class is the focus here. The change in this class needs to be notified to the observers. To do so, a list of observers who listen to this object is stored/removed by Attach/Deattach methods. Notify method ensures the observers of this object get the communication.

DoAction is the business logic method where modifications are made for the Subject class. The observer class implements the IObserver interface. For simplicity, it just has a method to receive the updates that happen in the subject object. Observers can also have their business logic based on the values that are updated.

The main method is the client here, where the objects of concrete observers A and B along with the subject are created. Then the observers are attached to the subjects, and the do action method is called in the subject. The same is observed in the observers. Later one of the observers is detached. Finally, the output after detaching is observed.

On executing the above code, the below output gets printed.

Conclusion

And that’s what Memento and Observer Behavioral design patterns are all about. Stay tuned for the next blog on the Design Patterns series.

Nieuwste