
Object Serialization with the Memento Pattern
by Budi Kurniawan09/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# |
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
ofEllipse
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'sControls
collection to theArrayList
, and then serializes theArrayList
into a file having the specified filename. If the filename is null, the application will try to call theSaveAs
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 |
