WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Object Serialization with the Memento Pattern

by Budi Kurniawan
09/09/2002

In my previous article, "C# Object Serialization", I explained how you could persist objects into a file. However, in many cases, not all objects are suitable for serialization. Some objects contain sensitive data that should not be saved into a file. Other objects are too bulky to persist in whole, so you may want to choose to persist only part of the objects. In this article, I discuss more advanced object serialization using the memento design pattern. At the end of the article, there is a vector- based application, similar to the one in the previous article, that demonstrates the use of the memento pattern for object serialization. You should read the previous article, if you have not done so.

The object serialization technology in the .NET Framework works well and is an excellent technology. Once your application becomes more complex, however, you might face problems with object serialization. There are cases where it might not be possible for your object's class to implement the ISerializable interface or to mark it with the [Serializable] attribute.

For instance, in a vector-based drawing application, a shape may be represented by a class derived from the System.Windows.Forms.Control class. Unfortunately, even if you marked the class with the [Serializable] attribute, you still cannot serialize instances of the class.

Related Reading

Programming C#
By Jesse Liberty

In addition, there are two other problems to solve. The first relates to efficiency: suppose you have a very large object that you want to persist, but only a small fraction of the states inside the object need to be saved. Serializing the whole object will require greater space than necessary, not to mention that it will take more time to serialize it and later retrieve it.

The second problem has to do with security. You may want to protect some internal state of the object. In other words, you may want to persist some states but not some other states.

These issues make the object serialization technique seem useless, don't they? Not really. We can still use the .NET Framework object serialization technique, but with the help of the memento design pattern.

The idea of this pattern is simple. In this pattern, the object whose state you want to persist is called the originator. To apply the pattern, you create another object called the memento, an external object that will hold the states of the originator. Therefore, if you need to save the states of the variables a, b, c, and d from the originator, the memento will have the same variables a, b, c, and d.

But what if some of the states in the originator are private variables, which of course are not accessible from outside the originator? The solution to this problem shows the beauty of this pattern. The originator will have two extra methods. One is called CreateMemento and the other is SetMemento.

The CreateMemento method instantiates a memento object, copies all of the states that need to be persisted to the memento object, and then returns the memento object. Therefore, to persist the originator, you call its CreateMemento to obtain a memento object and then serialize the memento object.

The SetMemento method is used to get the states back. After you deserialize the previously-saved memento object, you call the SetMemento object of the originator by passing the memento object obtained from the deserialization. Clever, isn't it? It's now time to put theory into practice with a vector-based drawing application.

Example: Vector-based Drawing Application

In this vector-based drawing application, there is only one type of shape that the user can draw: ellipses. An ellipse is represented by the Ellipse class, which is derived from the System.Windows.Forms.UserControl class.

There are several advantages to deriving from the UserControl class. For example, each ellipse object can receive user events, such as mouse click and keyboard input. Each ellipse drawn is added to the Controls collection of the main form. Because each ellipse is a control, it is not necessary for the form to override its OnPaint method. Consequently, the Ellipse class is responsible for drawing itself by overriding the OnPaint method. When the ellipse objects need to be persisted, it is not practical to serialize Ellipse objects directly. Each Ellipse object carries with it the states from its base class and most have not been changed.

The Ellipse class's constructor accepts a System.Drawing.Rectangle object, which it uses to set its bounds. This Rectangle object provides the top-left corner position, width, and height of the Ellipse object. This Rectangle needs to be saved when the Ellipse object is serialized. In fact, this is the only state that we need to persist.

For this purpose, the Ellipse class contains two special methods: CreateMemento and SetMemento. The CreateMemento method constructs a Memento object, populates it with the necessary state, and returns the Memento object. The SetMemento method is useful when we need to reconstruct the Ellipse object after deserializing it from a disk file.

Example 1 offers the Ellipse class and Example 2 the Memento class.

Example 1. The Ellipse class


using System;
using System.Drawing;
using System.Runtime.Serialization;
using System.Windows.Forms;
  public class Ellipse : UserControl
  {
    Pen pen = Pens.Red;
    Rectangle rect;

    public Ellipse()
    {
    }

    public Ellipse(Rectangle rect) 
    {
      this.rect = rect;
      this.SetBounds(rect.Left, rect.Y, rect.Width, rect.Height);
    }

    override protected void OnPaint(PaintEventArgs e) 
    {
      Graphics g = e.Graphics; 
      g.DrawEllipse(pen, 0, 0, rect.Width - 1, rect.Height - 1);
    }

    public Memento CreateMemento() 
    {
      Memento memento = new Memento();
      memento.Rect = rect;
      return memento;
    }

    public void SetMemento(Memento memento) 
    {
      this.rect = memento.Rect;
      this.SetBounds(rect.Left, rect.Y, rect.Width, rect.Height);
    }
    
  }

Example 2. The Memento class


using System;
using System.Drawing;
using System.Runtime.Serialization;
 [Serializable]
  public class Memento
  {
    private Rectangle rect;

    public Rectangle Rect
    {
      get
      {
        return rect;
      }
      set
      {
        rect = value;
      }
    }
  }

Now, let's shift our attention to the application's form.

The client area of the form contains nothing. However, the form wires the MouseDown and the MouseUp events to the this_MouseDown and this_MouseUp event handlers, respectively. To draw a shape, the user clicks on the client area, drags the mouse, and releases the mouse button. The shapes drawn are added to the form's Controls collection.

The File menu item contains the following menu items: New, Open, Save, Save As, and Exit.

  • Clicking the New menu item clears the form's Controls collection, as well as the form's surface.
  • Clicking the Open menu item shows an Open File dialog, from which the user can select a file containing an ArrayList of Ellipse objects. Every ellipse will then be reconstructed as a child control of the form.
  • Clicking the Save menu item constructs an ArrayList object and adds all controls in the form's Controls collection to the ArrayList, and then serializes the ArrayList into a file having the specified filename. If the filename is null, the application will try to call the SaveAs method.
  • Clicking the Save As menu item shows a Save File dialog. The user can choose the directory to which to save the object file, and select a filename.
  • Clicking the Exit item causes the application to exit.

Pages: 1, 2

Next Pagearrow