📄 pat5f.htm
字号:
<A NAME="auto1048"></A>
<P>Here are two issues to consider when implementing the Memento pattern:</P>
<OL>
<A NAME="auto1049"></A>
<LI><EM>Language support.</EM>
Mementos have two interfaces: a wide one for originators and a narrow
one for other objects. Ideally the implementation language will
support two levels of static protection. C++ lets you do this by
making the Originator a friend of Memento and making Memento's wide
interface private. Only the narrow interface should be declared
public. For example:
<A NAME="auto1050"></A>
<PRE>
class State;
class Originator {
public:
Memento* CreateMemento();
void SetMemento(const Memento*);
// ...
private:
State* _state; // internal data structures
// ...
};
class Memento {
public:
// narrow public interface
virtual ~Memento();
private:
// private members accessible only to Originator
friend class Originator;
Memento();
void SetState(State*);
State* GetState();
// ...
private:
State* _state;
// ...
};
</PRE>
</LI>
<A NAME="state-incr-changes"></A>
<LI><EM>Storing incremental changes.</EM>
When mementos get created and passed back to their originator in a
predictable sequence, then Memento can save just the <EM>incremental
change</EM> to the originator's internal state.
<A NAME="undoable"></A>
<P>For example, undoable commands in a history list can use mementos to
ensure that commands are restored to their exact state when they're
undone (see <A HREF="pat5bfs.htm" TARGET="_mainDisplayFrame">Command (233)</A>). The history list defines a
specific order in which commands can be undone and redone. That means
mementos can store just the incremental change that a command makes
rather than the full state of every object they affect. In the
Motivation example given earlier, the constraint solver can store only those
internal structures that change to keep the line connecting the
rectangles, as opposed to storing the absolute positions of these
objects.</P>
</LI>
</OL>
<A NAME="samplecode"><A>
<H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0></A> Sample Code</H2>
<A NAME="auto1051"></A>
<P>The C++ code given here illustrates the ConstraintSolver example
discussed earlier. We
use MoveCommand objects (see <A HREF="pat5bfs.htm" TARGET="_mainDisplayFrame">Command (233)</A>) to (un)do
the translation of a graphical object from one position to another.
The graphical editor calls the command's <CODE>Execute</CODE> operation
to move a graphical object and <CODE>Unexecute</CODE> to undo the move.
The command stores its target, the distance moved, and an instance of
<CODE>ConstraintSolverMemento</CODE>, a memento containing state from the
constraint solver.</P>
<A NAME="auto1052"></A>
<PRE>
class Graphic;
// base class for graphical objects in the graphical editor
class MoveCommand {
public:
MoveCommand(Graphic* target, const Point& delta);
void Execute();
void Unexecute();
private:
ConstraintSolverMemento* _state;
Point _delta;
Graphic* _target;
};
</PRE>
<A NAME="auto1053"></A>
<P>The connection constraints are established by the class
<CODE>ConstraintSolver</CODE>. Its key member function is
<CODE>Solve</CODE>, which solves the constraints registered with
the <CODE>AddConstraint</CODE> operation. To support undo,
<CODE>ConstraintSolver</CODE>'s state can be externalized with
<CODE>CreateMemento</CODE> into a <CODE>ConstraintSolverMemento</CODE>
instance. The constraint solver can be returned to a previous
state by calling <CODE>SetMemento</CODE>. <CODE>ConstraintSolver</CODE>
is a <A HREF="pat3efs.htm" TARGET="_mainDisplayFrame">Singleton (127)</A>.</P>
<A NAME="auto1054"></A>
<PRE>
class ConstraintSolver {
public:
static ConstraintSolver* Instance();
void Solve();
void AddConstraint(
Graphic* startConnection, Graphic* endConnection
);
void RemoveConstraint(
Graphic* startConnection, Graphic* endConnection
);
ConstraintSolverMemento* CreateMemento();
void SetMemento(ConstraintSolverMemento*);
private:
// nontrivial state and operations for enforcing
// connectivity semantics
};
class ConstraintSolverMemento {
public:
virtual ~ConstraintSolverMemento();
private:
friend class ConstraintSolver;
ConstraintSolverMemento();
// private constraint solver state
};
</PRE>
<A NAME="auto1055"></A>
<P><A NAME="auto1056"></A>
<P>Given these interfaces, we can implement <CODE>MoveCommand</CODE> members
<CODE>Execute</CODE> and <CODE>Unexecute</CODE> as follows:</P>
<A NAME="auto1057"></A>
<PRE>
void MoveCommand::Execute () {
ConstraintSolver* solver = ConstraintSolver::Instance();
_state = solver->CreateMemento(); // create a memento
_target->Move(_delta);
solver->Solve();
}
void MoveCommand::Unexecute () {
ConstraintSolver* solver = ConstraintSolver::Instance();
_target->Move(-_delta);
solver->SetMemento(_state); // restore solver state
solver->Solve();
}
</PRE>
<A NAME="auto1058"></A>
<P><CODE>Execute</CODE> acquires a <CODE>ConstraintSolverMemento</CODE> memento
before it moves the graphic. <CODE>Unexecute</CODE> moves the graphic
back, sets the constraint solver's state to the previous state, and
finally tells the constraint solver to solve the constraints.</P>
<A NAME="knownuses"><A>
<H2><A HREF="#relatedpatterns"><IMG SRC="gifsb/down3.gif" BORDER=0></A> Known Uses</H2>
<A NAME="unidraw-use-meme"></A>
<P>The preceding sample code is based on Unidraw's support for connectivity
through its CSolver class [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=unidraw_framework" TARGET="_mainDisplayFrame">VL90</A>].</P>
<A NAME="auto1059"></A>
<P>Collections in Dylan [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=Dylan" TARGET="_mainDisplayFrame">App92</A>] provide an iteration interface that
reflects the Memento pattern. Dylan's collections have the notion of a
"state" object, which is a memento that represents the state of the
iteration. Each collection can represent the current state of the
iteration in any way it chooses; the representation is completely
hidden from clients. The Dylan iteration approach might be translated
to C++ as follows:</P>
<A NAME="auto1060"></A>
<PRE>
template <class Item>
class Collection {
public:
Collection();
IterationState* CreateInitialState();
void Next(IterationState*);
bool IsDone(const IterationState*) const;
Item CurrentItem(const IterationState*) const;
IterationState* Copy(const IterationState*) const;
void Append(const Item&);
void Remove(const Item&);
// ...
};
</PRE>
<A NAME="auto1061"></A>
<P><CODE>CreateInitialState</CODE> returns an initialized
<CODE>IterationState</CODE> object for the collection. <CODE>Next</CODE> advances
the state object to the next position in the iteration; it effectively
increments the iteration index. <CODE>IsDone</CODE> returns
<CODE>true</CODE> if <CODE>Next</CODE> has advanced beyond the last element
in the collection. <CODE>CurrentItem</CODE> dereferences the state
object and returns the element in the collection to which it refers.
<CODE>Copy</CODE> returns a copy of the given state object. This is
useful for marking a point in an iteration.</P>
<A NAME="auto1062"></A>
<P>Given a class <CODE>ItemType</CODE>, we can iterate over a collection of
its instances as follows<A NAME="fn7"></A><SUP><A HREF="#footnote7">7</A></SUP>:</P>
<A NAME="auto1063"></A>
<PRE>
class ItemType {
public:
void Process();
// ...
};
Collection<ItemType*> aCollection;
IterationState* state;
state = aCollection.CreateInitialState();
while (!aCollection.IsDone(state)) {
aCollection.CurrentItem(state)->Process();
aCollection.Next(state);
}
delete state;
</PRE>
<A NAME="auto1064"></A>
<P>The memento-based iteration interface has two interesting benefits:</P>
<OL>
<A NAME="auto1065"></A>
<LI>More than one state can work on the same collection. (The same
is true of the <A HREF="pat5dfs.htm" TARGET="_mainDisplayFrame">Iterator (257)</A> pattern.)</LI>
<A NAME="auto1066"></A>
<P></P>
<A NAME="auto1067"></A>
<LI>It doesn't require breaking a collection's encapsulation
to support iteration. The memento is only interpreted by the
collection itself; no one else has access to it. Other approaches to
iteration require breaking encapsulation by making iterator classes
friends of their collection classes (see
<A HREF="pat5dfs.htm" TARGET="_mainDisplayFrame">Iterator (257)</A>). The situation is reversed in the
memento-based implementation: <CODE>Collection</CODE> is a friend of the
<CODE>IteratorState</CODE>.</LI>
</OL>
<A NAME="qoca-use-memento"></A>
<P>The QOCA constraint-solving toolkit stores incremental information in
mementos [<A HREF="vfs.htm?doc=bib-0.htm&fid=bb&hid=qoca" TARGET="_mainDisplayFrame">HHMV92</A>]. Clients can obtain a memento that characterizes
the current solution to a system of constraints. The memento contains
only those constraint variables that have changed since the last
solution. Usually only a small subset of the solver's variables
changes for each new solution. This subset is enough to return the
solver to the preceding solution; reverting to earlier solutions
requires restoring mementos from the intervening solutions. Hence you
can't set mementos in any order; QOCA relies on a history mechanism to
revert to earlier solutions.</P>
<A NAME="relatedpatterns"></A>
<H2><A HREF="#last"><IMG SRC="gifsb/down3.gif" BORDER=0></A> Related Patterns</H2>
<A NAME="auto1068"></A>
<P><A HREF="pat5bfs.htm" TARGET="_mainDisplayFrame">Command (233)</A>: Commands can use mementos to maintain
state for undoable operations.</P>
<A NAME="auto1069"></A>
<P><A HREF="pat5dfs.htm" TARGET="_mainDisplayFrame">Iterator (257)</A>: Mementos
can be used for iteration as described earlier.</P>
<A NAME="last"></A>
<P><A HREF="#intent"><IMG SRC="gifsb/up3.gif" BORDER=0></A><BR>
<A HREF="pat5gfs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/rightar3.gif"
ALIGN=TOP BORDER=0></A> <A HREF="pat5gfs.htm"
TARGET="_mainDisplayFrame">Observer</A><BR>
<A HREF="pat5efs.htm" TARGET="_mainDisplayFrame"><IMG SRC="gifsb/leftarr3.gif"
ALIGN=TOP BORDER=0></A> <A HREF="pat5efs.htm"
TARGET="_mainDisplayFrame">Mediator</A>
</P>
<HR>
<A NAME="footnote7"></A>
<SUP>7</SUP>Note that our example deletes the
state object at the end of the iteration. But <CODE>delete</CODE> won't
get called if <CODE>ProcessItem</CODE> throws an exception, thus creating
garbage. This is a problem in C++ but not in Dylan, which has garbage
collection. We discuss a solution to this problem on
<A HREF="vfs.htm?doc=pat5d.htm&fid=5d&hid=clean-up_proxy_for_iterators" TARGET="_mainDisplayFrame">page 266</A>.
<A HREF="#fn7"><IMG SRC="gifsb/up3.gif" BORDER=0></A></P>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -