<!-- Article image -->
<!-- Add the rest of your HTML here -->
This is the first part of perhaps four articles describing an approach to building applications that greatly reduces the complexity of their implementation. Simultaneously the approach makes the creation of advanced features, like undo or distributed editing, almost trivial to implement.
The code was implemented as part of another personal project and is based on ideas developed having worked on a market leading commercial application.
This article presents PomDemo, a very trivial application allowing red blocks to be created, changed to green blocks and then deleted. The application nonetheless demonstrates some advanced features:
- It contains a model for complexly constructed nested dialogs whose state can be modified and subsequently committed or discarded.
- It implements infinite undo and redo of changes.
- It allows the same data to be edited simultaneously within multiple instances of the application.
Future articles, or versions of this article, will expand the ideas presented here to do the following:
- Improve the underlying change mechanism to work more optimally.
- Allow the objects to be represented using other object technologies, specifically COM and .NET.
- Extend the technology to work efficiently with arbitrarily large object models.
In this first version of PomDemo the framework pattern is implemented using two basic classes, together with a small family of derived classes, templates and macros. The two basic classes are
CPomObject from which objects in the model are derived and
CPomLayer which is used marshal access to the object model.
What a typical application wants
This is a brief look at what I believe an application typically wants to achieve.
When an application starts it will read or attach to a copy of a stored object model. This aspect of the application is neither approached nor changed by the framework described here.
When an application closes, or indeed at any point during its life, it will want to write or update the stored object model; while this may involve transferring the whole object model, ideally it would involve just writing those parts that have changed. An application may store its object model in a relational database and just want to update those rows that have changed. The ability to know what has changed, and specifically from what to what, is core to the presented framework.
Some actions have immediate effect on an object model. Deletions, while possibly guarded by an "are you sure" dialog, once instigated occur without further delay. Such actions can typically be reversed using an undo command.
Other actions have delayed effect and comprise a dialog that may require many user interactions. Upon completion such actions can be reversed using an undo command. More than that before completion the user may chose not to go ahead with the changes, perhaps pressing a Cancel button. While changes are being made but not committed it is often convenient to be able to place changes into the object model in a way that allows them to be easily removed, the ability to do this is also core to the presented framework.
The problems of multiple step user actions can be compounded when they also have multiple layers. A property sheet in which changes have been tentatively made may have a child dialog. This child dialog may need to know the changes made in the parent property sheet, but both changes made in the child dialog or in the property sheet need to be appropriately reversed should the user cancel one or both of them.
For many years now it has been a reasonable expectation on the parts of users that applications provide them with a means to undo their last few actions and subsequently redo them. This is similar to the other problems, a matter of producing a command object that can reverse the changes to implement undo and reapply them to implement redo.
Not seen by the user, but in the mind of developers, is the need to reflect changes in the user interface. With a large object model it may not be practical to simply redraw everything on the screen. When information is held in controls, emptying and refilling them unnecessarily may lose state information that could surprise the user. Ideally only those parts of the user interface that have changed need to be redrawn or otherwise update.
The framework presented here also allows separate object models to be kept in synchronisation. This could be used to allow several users to simultaneously edit the same object model. In the personal project the framework is intended to be used in there is simultaneous access and update of the object model by both user and background tasks.
Trying to implement the above in isolation from one another can lead to unwieldy and complex code. Using MFC and Class Wizard in Developer Studio 6 to implement a dialog, for each attribute to be presented there would be a line of code in the caller to place the value into the dialog object and a line of code to extract it should the object's DoModal return IDOK. While this could be refined, complexity quickly starts arising and an increasing amount of the developers' time is spent writing housekeeping code. Using this framework to implement a dialog you need only pass a pointer to the object the dialog works on, no explicit housekeeping needs to be done by the developer.
What PomDemo does
PomDemo manipulates an object model entirely held in memory, that being perhaps the simplest manifestation of the animal. The object model in question only has two classes,
CMyObject that stores information about a coloured block and
CMyObjectCollection that holds the current list of these. This object model demonstrates objects being created, modified and destroyed.
Looking at the implementation of the objects,
CMyObjectCollection really is simply a collection of
CMyObjects and contains a single data member:
CList<const CMyObject*, const CMyObject*> m_myObjectList;
The real currency of the application is
CMyObject, another unusually simple class consisting of two data members, a screen co-ordinate for the block and a current state for the block:
A block starts life as
stateNew in which it is painted red on the screen, if we touch the object it changes to
stateChanged and is painted green. Touching an object a second time deletes it, or at least marks it deleted using
PomDemo in action
PomDemo is a simple MDI application without file support. Opening PomDemo will present you with an explorer style window containing a blank document. All user actions on the document result from either double clicking or right clicking in the right pane. Double clicking and right clicking produce exactly the same results, in the implementation double clicking is implemented through longhand code to show what is going on whereas the same operations are coded for right clicking using templates.
Double clicking in the right pane on the background will create a new red block. Double clicking a red block will turn it green. Double clicking a green block will delete it. This is only a demonstration so the application has no concept of selection or Z order.
When the first red block is created the Undo button on the toolbar becomes enabled. If the button is pressed the red block will be removed and the Redo button would become available.
The Undo button behaves exactly as a user would expect. If five changes are made, pressing Undo fives times reverses those changes.
The behaviour of the Redo button might give room for argument; it always attempts to redo the last undone action for that layer, even if you have made intervening changes. This is only a demonstration; a real application would probably discard the redo queue whenever a normal user action was made.
The layer tree
The left pane contains a tree control, initially with a single item in it labelled Root. New layers can be created using the Layer New menu item which produces the following dialog:
The name entered in the edit box has no significance other than it is displayed next to the layer in the tree.
In the version of PomDemo presented here the check box is never enabled. In future versions the check box is enabled when the parent layer is the key, identified with a . Non-key layers are marked . If the check box is checked then the parent item ceases to be the key and the new child inherits that role. The key layer is the one that contains the real object model.
When the selected layer in the tree is a leaf the Layer Delete menu item becomes available. Using the item will remove the selected layer from the tree. In future versions this is true even it is the key layer marked selected. It is normal for the real object model to be a member of the layer tree, but this is not a requirement of the framework.
Unless something is done that induces conflict, changes made to one layer appear in that layer and all of its descendent layers. If there are just two layers, the initial layer Root plus a created layer Child, all changes made to the right pane when Root is selected are duplicated in Child, however changes made to the right pane when Child is selected do not change Root. This is best observed by using Window New to open a second window. This effect is unchanged regardless of whether the key is Root, Child or neither.
With the exception of the key layer, layers store the changes between themselves and their parent. These changes can be made visible by using the toolbar button. When the button is depressed, rather than just displaying solid blocks the right pane presents the following objects:
|Solid red block, an item created in this layer.|
|Solid green block, a changed item originally created in this layer.|
|Diagonally hatched red block, an unchanged item from a parent layer.|
|Diagonally hatched green block, a changed item from a parent layer.|
|Cross hatched green block, an unchanged item from a parent layer that has been changed in this layer.|
|Cross hatched black block, an item from a parent layer that has been deleted in this layer.|
|Cross hatched red block, a changed item from a parent layer that has become unchanged in this layer, an interesting possibility resulting from carefully sequenced use of Undo.|
It is possible to make changes to the object models presented in different layers that are incompatible with one another. In PomDemo it is clearly incompatible in one layer to change a block from red to green and in another layer to delete the same block. If this happens where the layers are ancestor and descendent a conflict results, in the framework this results in a conflict in the descendent layer.
The framework includes a mechanism for resolving conflicts, but where no resolution is possible it merely signals that there has been a conflict and reverses all the changes made in the conflicting layer.
In PomDemo conflicts in the collection of items are constantly occurring and being silently resolved. Conflicts in the items themselves are never resolved; they always result in the layer's changes being discarded. If a layer is being displayed when a conflict occurs it its background turns grey, a single left click on the background will clear this indicator.
Commit and Discard
When changes have been made to a layer other than the root two important toolbar buttons become available, Commit and Discard . Their functions are related, Commit makes all changes in the selected layer appear in its parent layer, Discard removes all the changes. Another way to view this is that Commit makes the parent layer the same as the child whereas Discard makes the child layer the same as the parent.
The layers within PomDemo can be viewed as modelling layers within a user interface, so Root could be the application's current object model, a child the object model as viewed from an open property sheet and a grandchild the object model as viewed from a dialog opened from the property sheet. Commit corresponds to the result of pressing the OK button on the corresponding user interface component while Discard represents pressing Cancel.
Synchronising object models
PomDemo can do something perhaps not seen very often. On the Layer menu the last two items are Share and Connect:
The Layer Share option, which is available for every layer, presents the following dialog:
The Port box refers to a TCP/IP port and selecting an available one will share the layer using that port of the host machine. Having shared a layer its icon will be overlaid with the usual Windows sharing hand, becoming either or .
Once shared the Share menu item will become Unshare, the selection of which will close the listening socket. Connections established through the share will still remain active.
The Connect item is only available when an empty layer is selected. When Connect is selected the following dialog appears:
The name or IP address of a machine sharing a layer is entered into the Server box together with the corresponding port in the Port box. When this is done successfully three things happen immediately. Firstly the name of the currently selected layer changes to have a one in parenthesis appended to it, perhaps , assuming no parenthesised number was already present. Secondly the corresponding layer on the server machine has a one in parenthesis appended, again assuming no parenthesised number was previously present. These numbers are merely a count of the connections active on the layer, they will be seen to increase and decrease as connections are made and broken.
The third thing that happens is that the previously empty layer is filled with whatever is in the corresponding layer of the server layer.
Once connected, changes made to one layer view of the object model are reflected in all connected layers. Thus two people can be creating and modifying blocks in PomDemo on two workstations simultaneously.
PomDemo allows connections to be made this way between any layers or machines, even layers within the same hierarchy. Clearly many possible topologies are meaningless or not useful, but likewise there are many useful ways of making connections.
A real application would have some handshaking to ensure that a change sent from one peer to another arrives and executes before another is sent, together with many other sanity checks. PomDemo is only a demo and carries no such code, it is possible to abuse the code such that it stops working.
The PomDemo source code
PomDemo does not do much editing, there is only one user action really to respond to, clicks. All of the work is really in the right hand pane's
In engineer Utopia we would perhaps just like to write the code to express what we want to do and nothing else. In the case of PomDemo this code might read something like the following:
void CMyDemoView::OnLButtonDblClk(UINT ,
CMyObjectCollection* const pMyObjectCollection =
CMyObject* pMyObject =
if (pMyObject == NULL)
CMyObject* const pMyObjectNew = new CMyObject(point);
Unfortunately in the real world we are forced to do a little more work. The Utopian code neglects several important aspects of the PomDemo user interface, there are no hooks to update the display or code to handle the undo logic.
PomDemo allows editing to take place through both double clicking and right clicking. The double click code described here uses
CPomLayer to make changes and includes some housekeeping code. The right click code makes exactly the same changes in the same way but, through the use of some templates, is exactly the same length as the Utopian code (it actually gains one statement at the top and loses the last statement at the end).
Looking at the code with the exposed housekeeping,
CMyDemoView::OnLButtonDblClk starts with an extra line not in the Utopian code:
void CMyDemoView::OnLButtonDblClk(UINT ,
CPomLayer pomLayer(m_pMyLayer, false);
This creates a
CPomLayer object, all changes to the object model are managed by layer objects, changes can only be added to leaf layers. Creating a new layer gives the context in which new changes can be made.
The function closes with the following:
This causes all the changes made within function to become part of the layer above. Side effects of doing this include recording the changes for Undo in the parent layer (the layer visible in the right pane that was clicked on) and causing the changes to appear on the display.
In a template version a class
CPomChangeLayer derived from
CPomLayer is used.
CPomChangeLayer maintains a stack of layers, the top of which is available through a static member function, and also automatically calls
Commit in its destructor.
Reading the collection
In the Utopian version we just got the collection straight out the object model:
CMyObjectCollection* const pMyObjectCollectionRead =
OnLButtonDblClk we need to obtain it through
const CMyObjectCollection* const pMyObjectCollectionRead =
CPomLayer::GetReadObject will look for a locally changed version of its parameter and returns that if found. If no change is found then non-root non-key layers will call the same member of their parent. Finally if the function is called against the key layer the parameter is returned unchanged.
The right click version uses a template pointer that will obtain the read pointer from
CPomLayer::GetReadObject when it is required.
Doing the hit test
The original code was as follows:
CMyObject* pMyObject =
OnLButtonDblClk code reads.
const CMyObject* pMyObjectKey =
The two important variables have had their names decorated with suffixes, the result is a pointer to a
const object and
pomLayer is passed to
HitTestObject. Whereas the Utopian code has one pointer values through which it accesses data,
CPomLayer identifies four values:
|Key ||Pointer to the real object in the object model, or the object as it is originally allocated if it has not yet been incorporated into the object model. This pointer may be stored as a pointer to a |
const object so that its value is not inadvertently changed.
|Read ||Pointer to the (|
const) object from which values should be read for a given layer. For most pointers in most layers this can be expected to be the same as the key pointer. Obtained using
CPomLayer::GetReadObject and the key pointer.
|Write ||Pointer to the (non-|
const) object that may be modified for a given layer. The pointer is obtained from
CPomLayer::GetWriteObject using the key pointer, a new object will be allocated for the layer the first time it is called.
|Change ||Pointer to the (|
const) object that corresponds to the changed value in the layer. Obtained from
CPomLayer::GetChangeObject, it is very similar to that from
CPomLayer::GetWriteObject except if no object has been allocated
NULL is returned. A number of iterators are also provided for accessing these values.
The right click version only differs from the Utopian by using a template pointer.
Creating a new block
In Utopia we just create a new block thus:
if (pMyObject == NULL)
CMyObject* const pMyObjectNew = new CMyObject(point);
OnLButtonDblClk we just have to do one extra step:
if (pMyObjectKey == NULL)
CMyObject* const pMyObjectNew = new CMyObject(point);
CreateObject call informs
CPomLayer that it needs to manage the life of a new object. In the right click template version a
CreateObject that returns its parameter is employed instead of the one in the base class.
PomDemo manually manages the life of objects, thus when an object is created by the framework to stand in for another it needs to be delete by the framework when it is no longer needed.
CreateObject gives the framework the information it needs to do this.
Applications that use reference counting or garbage collection should not need to call
CreateObject, objects can be created silently given that the framework sees changes to objects referring to the new arrivals.
Inserting the new member
The newly created object needs to be added to the list of objects, in the Utopia version this simply:
OnLButtonDblClk cannot simply do that because the pointers it has, the key version from
CMyDemoDoc and the read version
const and not intended for that purpose. Thus it needs to obtain a non-
const write version and manipulate that:
CMyObjectCollection* const pMyObjectCollectionWrite =
Doing that, together with the
Commit line at the end of the function, completes the new object's creation. The template version hides the extra detail.
Changing the object
In Utopia we just changed the object by calling its
Exactly as was the case with the collection object when we were creating a new object, the only pointer to the object
OnLButtonDblClk has initially is
const, so it needs to get a non-
const version and touch that instead:
CMyObject* const pMyObjectWrite =
Perhaps the layer should have been passed to
Touch, but for the moment it does not deal with pointers to other objects and so it is completely safe to leave this detail out.
Touched an object may indicate that it does not want to go on living! In the Utopian version it was simply put out of its misery as follows.
IsDeletedObject is actually one of the
CPomObject base class functions. (Having
Object at the end of the name is a bit redundant, it was done to avoid conflicts where
CPomObject was introduced as a new base to an existing class.) The
OnLButtonDblClk code follows:
CMyObjectCollection* const pMyObjectCollectionLayer =
As before a writeable version of the collection needs to be obtained before the unwanted object's pointer can be removed from it. As always this extra step is avoided in the template using version.
In the Utopian code we had to delete the unwanted object. In
OnLButtonDblClk the unwanted object is not quite unwanted, it is still needed in the parent layer and may be wanted in other layers. Thus
CPomLayer takes over the responsibility for deleting the object, thus removing the
The CPomObject base class
In order for
CPomLayer to work it requires the members of object models to be derived from
CPomObject. The base class has an empty constructor and destructor and no data members. It defines five virtual function members, two of which are pure. Three member template functions (and three accompanying macros) are provided to help writing override implementations.
virtual CPomObject* CreateCopyObject() const = 0;
This is the first of the pair of pure virtuals. The function creates a clone of the original.
CPomLayer uses this to create the objects returned the first time
GetWriteObject is invoked for an object in that layer.
In a real application there is likely to be a derived class or two between
CPomObject and the actual concrete classes. One of these classes might provide support for automatically cloning the object. Conceivably an MFC object could do this with little or no work. A new object of the same type could be created using
CRuntimeClass::CreateObject and its data set using
In a real application you might not actually want to exactly clone the whole object.
CMyObjectCollection in PomDemo contains a list of objects which, if it were a real and useful application, could be extensive. A better solution might allow the copy to keep a pointer to the original and just record the differences made to it, in much the same way
CPomLayer records changes to the model as a whole.
virtual void OverlayObject(const CPomObject* const
pPomObjectMerge) = 0;
The second required pure virtual in
CPomObject. This is used to copy changes back to the original object when
Commit is called.
For some limited objects implemented using MFC you could implement this by serializing out of one object into the other. Another method that might work in an MFC application is just
memcpying one object over the other, this would work with simple types and pointers but would cause serious problems with
CStrings, other more complex types and information carried in the class that is not part of the object model proper.
The remaining functions are not required, but in a real application better default implementations might be provided.
virtual CPomObject* CreateMergedObject(const CPomObject* const
const CPomObject* const pPomObjectDaughter) const;
This is the only function of the seven that might be non-trivial to implement. The function merges two descendant objects back into their common ancestor. Except where one of the two descendants is found to be the same as the original ancestor the default implementation just gives up.
CMyObjectCollection contains some fairly dumb code to merge its list of objects together:
- If an object still appears in both descendant versions, it still appears in the merged version.
- If an object has been removed from just one of the descendants, it does not appear in the merged version.
- If an object has been added to just one of the descendant versions it appears in the merged version.
- Otherwise, if a new object has been added to both changed versions or removed from both changed versions,
CMyObjectCollection gives up and returns no object.
CMyObject does not override the function, mixing changes is always considered incompatible.
It might be argued that if two layers make the same change then there is a good case for believing that the change is one that should be made. In some applications this could be the case. In the general case it is not. Consider an object that contains an attribute containing a count of some other objects in the model. If two layers both increment the counter because they have added new countable objects, accepting the new value as the correct merged value is not valid, it should be N+2 not N+1.
virtual bool IsDeletedObject() const;
If an application inherently knows when the end of an objects useful life has come then this function does not need to be implemented. If an application needs to delete its objects manually, using
delete in the absence of this framework perhaps, then rather than actually using
delete an application should mark unwanted objects such that this function returns
true. When the framework knows an object is no longer needed it will arrange for its deletion.
It is interesting to note that the framework actually has little interest in the creation and deletion of objects, indeed it has little interest in the object model outside of what is being changed. What interest it has really stems from its need to ensure there are no memory leaks and that it does not maintain versions of objects unnecessarily.
virtual void ReleaseObject();
The framework does not simply
delete objects as there may be subtleties to the way objects are disposed of. Specifically in a reference counting system deletion is implied rather than explicit. When an object is no longer needed this member is called in order to signal the framework has no further interest in it.
The default implementation of the function is
delete this. Perhaps this function should have been made pure virtual.
This article has already presented many of the features of
CPomLayer. This section will pose and, hopefully, answer some questions about the class in order to hopefully flesh out more of its design and purpose.
Where is the object model?
The object model itself is really outside the scope of
CPomLayer which really only concerns itself with changes within the model. Other than when there is change occurring within the object model
CPomLayer maintains no pointers to it.
Why would you not have a key layer?
If you have a legacy application then you may have only partially adopted the scheme described here. Thus changes to the object model proper may be made through some other mechanism.
It is also possible that you never want to actually touch the object model, only to store the differences. Perhaps you have a game with a large and complex description of the environment and just want to track the player's interactions with it.
Why would you want the key layer anywhere other than the root?
An application may read a great deal of data from a database. The version of the object model from the database may be presented as the root layer. A child layer may then represent the object model as it has been modified by the user. Initially these will be identical and when the user saves their changes back to the database they will become identical once more.
When the user saves changes back to the database the only thing of real interest is the difference between what was stored and what needs to be stored, having the original object model available unedited is of no value.
If a user makes many changes to the object model and these are stored as changes to some base line, then it is possible that the process of substituting the edited version of an object for the real one might become very processor intensive. Thus having the most frequently referenced layer available as a plain text version of the object model may be important.
When are changes observed?
The application may only obtain writeable copies of objects from leaf layers. All other changes are made by calling
Discard. As these functions are called virtual members are called within
CPomLayer to inform derived classes of progress.
Of special interest here is
OnChange, this gets called before a layer has new data merged into it. One parameter to
OnChange contains the layer being merged, that is to say what this layer is going to become, while the other parameter is a Boolean indicating whether the change is the result of a
Commit down from a child, as opposed to a change resulting from a
Commit coming up from a parent. Where something is being kept in synchronisation with the object model, all changes need to be noted. Where user changes are being recorded in order that they may be replayed later in some way, then only those from
Commit operations are of interest.
Two helper classes
CPomObserver are used to distribute
OnChange notifications as per the Observer Design Pattern.
CPomObservedLayer is derived from
CPomLayer and maintains a list of
CPomObserver objects. When
OnChange is called on
CPomObservedLayer it relays the call onto all the
CPomObserver objects in its list. In PomDemo
CPomObserver is one of the base classes for the right pane view, thus it gets a kick whenever anything needs to be changed.
The actual concrete layer class used in PomDemo also overrides
OnChange. Here it produces a
CMyObjectCommand, part of the implementation of the Command Design Pattern. This is a computed object that contains a list of all the changed
CMyObject instances. Just three possibilities exist, creation, modification and deletion.
CMyObjectCommand actually stores a sequence of before and after pairs, the before value is not stored for creations, the after value is not stored for deletions.
When the change is due to a
Commit the change is added to a list and is used to implement Undo.
If connections are present then the
CMyObjectCommand object is transmitted to all the peers.
How are changes observed?
As indicated above changes are notified by passing round the before and after layers. In
CPomLayer the before layer is itself, in
CPomObserver it is passed explicitly as a parameter.
There are several ways of actually discovering changes between the layers. By brute force you could just iterate over both views of the object model and deduce the changes.
CPomLayer does, however, provide more straightforward ways of finding the changes.
GetWriteObject there is a third function
GetChangeObject. Unlike the other two, which are guaranteed to return a pointer to an object of the same type as the input object,
GetChangeObject only returns an object if it has changed between this layer and the previous. It is called on the after layer to detect changes to important objects, like the
CMyObjectCollection instance in PomDemo.
It is also possible to enumerate through all the changes using
GetNextChange, implemented here as iterators in the MFC style. Called on the after layer these enumerate what has changed. PomDemo uses these to find the objects that have been deleted.
CPomChangeIterator builds on the MFC style iterators to provide a more STL style of iteration over objects of the same type.
What goes on inside CPomLayer?
CPomLayer does not have to do much. Often there is a simple stack of layers, copies of objects to be made on the top of the stack are progressively overlaid onto the objects below. Reproducing such function is simple.
CPomLayer has to do an awful amount of hard work. If the key layer is above the root and changes are committed to it, in order that the object model viewed from the root does not change compensating entries need to be made.
The initial version of
CPomLayer accompanying this article has been reasonably well tested with non-reference counted object models where conflicts are avoided. You can use the simple block interface directly. If you click on the icon of the About dialog some extensive regression tests are run. There are likely to be bugs lurking in its handling of conflict situations and merging of non-root key layers, hence why some features were stripped from this version..
The implementation of
CPomObject presented here are just enough to implement PomDemo and the personal project that required the code in the first place. The classes and the notions can be expanded in several directions, some of which are touched on below.
Reference counted and garbage collected versions
Much of the code presented here assumes objects are explicitly created and later explicitly deleted. Some small changes are needed to enable a paradigm where object lifetimes are dictated by reference counts or garbage collection. For reference counting the difference between the two paradigms lays in which event
CPomLayer needs to be interested in, creation or deletion.
COM interface version
While the code here is all about pointers to C++ objects, changing the notion to apply to COM interfaces, or indeed CORBA or any other object technology, is not so difficult to imagine.
Except as already noted, when things become complex and changes are made between the root and key layer, no data in parent layers is ever touched until such time as
Commit is called. This gives the opportunity to have threads that open their own layer and make wide ranging changes to their private view of an object model without needing to synchronise with other threads until one of them needs to
There is a notion that, given the facility to Undo a mistake, a user will always reverse changes by using the Undo feature. This is not always true, a user inadvertently creating an unwanted object may simply delete it them self rather than pressing Undo. Similarly a user may make mistakes on purpose, using global search and replace to change 425 occurrences of "their" to "there" before changing back the three occurrences where they actually meant to write "their."
There may be situations where combing through the changes in before and after layers looking for non-changes may result in gains, if less time is spent spotting non-operations than might be spent executing them.
More communicative version
Currently there are only two virtual functions that can usefully be overridden from
CPomLayer to track its changes in state. More virtual functions could be added to provide richer information as to what is going on. It may often be useful to maintain pointers to potentially changed, and hence moved, objects for extended periods of time. A virtual function that informed the application of changes of address could be deemed useful.
CPomLayer as presented and the extensions proposed for
CPomLayer to support add more weight to the code and ultimately slow it down. Versions of
CPomLayer that cut out support for unwanted features could be generated, as indeed could custom versions to fulfil requirements of specific projects.
Some of the implementation of
CPomLayer is already non-optimal. There are objects created and immediately destroyed as part of the notification and change propagation mechanism. Some more analysis for specific implementations could reduce this.
MFC free version
PomDemo and the personal project the code was written for are targeted at the Microsoft Windows platform and have been coded using MFC, not that MFC is particularly tied to that platform. The ideas contained can be freely translated to STL, .NET or any other frameworks or libraries.
Load object model on demand
All access to objects in the object model first pass through
CPomLayer using a pointer to the subject object in the real object model. There is no reason why this pointer needs to be dereference other than within
CPomLayer, so the pointer does not really need to be in the same address space as the current application, on indeed be a pointer at all. It could be arranged that an application is passed certain key pointers to objects within another address space. When the value behind a pointer is required
CPomLayer could check to see if a local copy has been loaded, if not it could obtain a copy. In this way only those parts of an object model that were referred could be loaded.
Another mechanism could be in place to unload objects that are not currently required.
Where object models are synchronised between several processes, knowing what objects are current used in fellow processes could reduce unnecessary change notifications being sent.
Database update racing
There may be applications where you wish to maintain your object model in a database but not have to wait for database updates to complete.
This could be implemented as four
CPomLayers. The bottom layer represents the database as it has been committed. The layer above that represents the changes that a thread is currently building a transaction to write to the database. The layer above that represents the outstanding changes that will be committed in the next database transaction. Finally the topmost layer of this stack represents the object model as seen by the application and which will periodically be committed to the
CPomLayer controlling the database thread.
About the author
Pete has worked for several famous name companies including Microsoft, Channel Four Television and Orchestream. He has been responsible for the architecture and implementation of systems ranging between broadcast, word processing and life assurance quotations. Currently he working at Invensys helping to create the next generation of their market leading building automation systems, but would far rather be in Spain driving between Paradores.