In a pretty big project, I have a hierarchy of objects, let's say:
Zone
object, which contains Block
objects and Connection
objects.
Mainliy, the Connection
object links together several Block
objects, all inside a Zone
.
In this project, I am using the Memento pattern (together with the Command Pattern) for implementing Undo/Redo functionallity.
Also, I am reusing the "Memento" implementation to implement also Save/Load functionallity, so the Undo/Redo propertis are always in sync with what is being Saved/Loaded.
Something among the lines:
//Base class for Zone, Connection and Block
class Object
{
virtual void save(IWriter *writer) = 0;
virtual void load(IReader *reader) = 0;
}
//Base classes:
class IWriter
{
virtual void writeInt(int x) = 0;
}
class IReader
{
virtual int readInt() = 0;
}
//The Memento just saves / reads data from a memory map
class Memento: public IWriter, public IReader
{
}
//These classes are used to save / load from file
class XmlWriter: public IWriter
{
}
class XmlReader: public IReader
{
}
Now, it is easy to do the Undo / Redo command for deleting a zone:
class DeleteZone: public QUndoCommand
{
void redo()
{
zone->save(&data);
//NOTE: zone will save its own data, and also all its children data, in order
//to restore completely the previous state.
delete zone;
}
void undo()
{
Zone *zone=new Zone();
zone->load(&data);
}
private:
Memento data;
}
And it is also easy to Save / Load the entire Zone:
XmlWriter writer("path to file");
zone->save(&writer);
So, using this patterns, the UI does not need to know anything about the internal logic of the model. It just saves / loads the data and it is done.
Now I wanted to repeat this pattern also for the Connection
object, which seemed similar.
Here is the problem:
When a connection is created, it should perform some operations on the related Block
objects. Let's call them connectionCreated()
This operations depend on the connections each Block
has, so there are two different situations:
- When only a connection is being created (like in a
CreateConnectionCommand
), thenconnectionCreated()
should be called immediately.
In this case, to prevent the UI to know anything about the internal logic, I was thinking to add the connectionCreated()
to the Connection
constructor.
- But when loading the entire
Zone
,connectionCreated()
should be delayed, until all the connections have been loaded.
So it is not valid the solution of adding it to the constructor. Here it should be added to the load() function of the Zone
, which is fine, BUT it should be also added to the CreateConnectionCommand
, which is part of the UI and thus it seems not fine.
AND also, the Memento object should save the Connection
state, together with the related objects for the case when a connection is being created, but not when the zone is created (since this will be handled by Zone::load()
), again leaking the model details to the UI undo/redo functionallity.
Is there some better pattern / approach to this situation?
Note 1:
connectionCreated() does some operations to update variables inside Block to ensure consistency, and it also creates some required objects which will be visible to the user.
Furthermore, to ensure consistency, it may create other connections, which in the case of Zone::load
may still to be created, thus duplicating objects.
This is why in the situation of Zone::load
, this operations should be delayed until all the connections are created, but not in the case of CreateConnectionCommand
, where only one NEW connection is being created.