65.9K
CodeProject is changing. Read more.
Home

Mementos

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.41/5 (11 votes)

Jun 2, 2005

4 min read

viewsIcon

55151

A basic IMemento implementation.

There is no download associated with this article. For some usage examples, check out:

Introduction

The IMemento interface is simple yet powerful. Classes that implement this interface can preserve and restore their state in any internal representation that they choose. Classes that interface to classes implementing IMemento can request that the instance save/restore its state without knowing the details of the save/restore mechanism. While this can deceive you into thinking that IMemento is a good way of managing persistent information, it is not. For that, the usual n-tiered pattern, which usually includes a data access layer, should be used. The IMemento is useful in storing localized state information regarding an object. This state information is disassociated with any other application information (as in, it does not have any relationship to any other information in the application).

Memento Design Pattern

The Memento design pattern is well known and you can read more about it here. The Eclipse project also has a good write-up on it here. One might not think that the Memento design pattern is worthy of an entire article, but I feel that it is such a useful pattern that it can stand on its own merits. Furthermore, this article describes extending the Memento design pattern to something that I think is more useable for real world applications. As the Memento design pattern stands, it is lacking some features.

The typical design pattern looks like this:

The reality is though, that one rarely uses a memento without also associating some contextual information about the memento. Why was the memento created? What did the user just do? What did the application just do? For this reason, I find it more useful to have the Memento object include an Action string:

If you read the documentation on the Memento design pattern, you'll notice that there is an "Originator" class that implements SetMemento and CreateMemento methods. For this mechanism to be truly useful, the class where you want to preserve/restore state information should ideally tell the application that it is capable of doing so, through its own interface, which I have called "ISupportMemento":

Now we have all the players on the stage--classes that support mementos always implement a standard interface mechanism, and the classes that implement the memento include some contextual information.

Interface Implementations

public interface IMemento
{
  object State
  {
    get;
    set;
  }

  string Action
  {
    get;
    set;
  }
}

public interface ISupportMemento
{
  IMemento Memento
  {
    get;
    set;
  }
}

A Concrete Memento Class

If you don't have to deal with multiple inheritance issues, you can derive your specialized memento class from a concrete Memento class rather than an interface. Usually this should be the case, since the memento is managed by a separate class rather than the originator class. The basic implementation looks like this:

public class Memento : IMemento
{
  protected object state;
  protected string action;

  public virtual object State
  {
    get {return state;}
    set {state=value;}
  }

  public string Action
  {
    get {return action;}
    set {action=value;}
  }
}

You will note that I am using properties instead of discrete GetState/SetState methods. Since the design patterns were originally written for C++, they don't always look like what you'd probably want to implement in C#. So, my actual IMemento interface looks like this, diagrammatically:

Who Manages State?

Note that there's quite a bit of flexibility with regards to which object creates the state information. I've illustrated two slightly different implementations. In this example, the class implementing ISupportMemento creates the state:

Example 1: Originator Creates The State Object

public class MyClass : ISupportMemento
{
  public IMemento Memento
  {
    get
    {
      Memento mcm=new Memento();
      mcm.State=GetMyState();
      return mcm;
    }
    set
    {
      SetMyState(value.State);
    }
  
  protected object GetMyState()
  {
    // ... implementation ...

  }

  protected void SetMyState(object state)
  {
    // ... implementation ...

  }
}

In this example, the class implementing the IMemento creates the state.

Example 2: Memento Creates The State Object

public class MyClass : ISupportMemento
{
  public IMemento Memento
  {
    get
    {
      IMemento mcm=new MyClassMemento(this);
      return mcm;
    }
    set
    {
      ((MyClassMemento)value).SetState(this);
    }
  }
}

public class MyClassMemento : Memento
{
  public MyClassMemento(MyClass mc)
  {
    // get MyClass state by querying properties of MyClass instance.

  }

  public void SetState(MyClass mc)
  {
    // set MyClass state by setting properties of MyClass instance.

  }
}

The advantage with the second implementation is that you can change the persistence mechanism without affecting the originator implementation--it is independent of the originator class. However, the implementation is a bit more complex and relies on casting the IMemento to the appropriate concrete memento implementer, which can be prone to error if the wrong instance is passed in.

Conclusion

I specifically wanted to avoid the topic of "how do you actually create the state?" There are many answers--maybe the state is a simple value. Maybe you want to use XML serialization, or binary serialization. In the first example, the originator has the smarts to persist itself. In the second example, you may want to decorate properties with an attribute that provides information about what properties should be persisted. You may want to include default values. So, as you can see, the issue of persistence is itself complex.

One final note--you'll find the memento pattern used frequently in undo/redo buffers, and this is why I've also included the Action contextual information in my IMemento implementation.