Sometimes I catch myself applying a particular pattern in an architecture / application design almost without consciously thinking about it. One of the side benefits of flailing around in Cocoa and Objective-C is the disruption of my “pattern muscle memory”.
A recent example is in the implementation of the model layer of the graphical authoring tool I’m working on. One very basic required feature is the ability to delete a graphical element. The element can be deleted in the usual ways: the user pressing the delete key, the user selecting delete from the edit menu, etc. These actions ultimately invoke the same code and remove the item from the data model. There are other ways that the element might be deleted too: as a result of a cut operation, as the result of a synchronization or merge operation with an external source (like importing data from another file), etc.
My pattern muscle memory would normally have me tackle the ability to undo/redo this operation using classic “gang of four” style command and memento patterns; creating concrete implementations of an abstract command for each action.
In this case, being a new Cocoa programmer, my C#/.NET pattern muscle memory was disrupted by the NSUndoManager’s cool ability to package up “ordinary” invocations rather than requiring any special sub-classing or glue code. Now, I could certainly implement this kind of thing in .NET using reflection, etc, but I would have to implement it rather than using it “off the shelf”. Besides, my pattern muscle memory would have kicked in before I really had given it too much thought.
For my delete implementation, I chose to use one of the common Cocoa approaches exemplified in Aaron Hillegass’ book of using KVO to observe the removal of the object from it’s collection and registering it’s inverse (an insertion) on the undo stack. When KVO tells me that the object was added, I register the inverse of that operation (a deletion) on the undo stack.
One common challenge I’ve faced in the past is what to do about side effects or otherwise dependent actions of the principal action occurring. In the case of deleting my graphical object, there are several side such effects. One example is the need to modify connected or constrained graphical objects. In certain situations, an extreme side effect is the need to delete or create related objects, allocate unique identifiers, etc.
My pattern muscle memory typically has me encapsulate these dependent actions along with the implementation of my original user operation, in this case, the delete. One complication of this approach is that as the application scales and multiple developers work on the code base, it can be hard to ensure that everyone consuming the model uses the same high level delete implementation.
In the case of the delete key, the delete menu item, etc, it’s dead simple to make sure the same high level code (with it’s encapsulated side effect behavior) is invoked. As more complex features (such as the merge example) find themselves needing to delete the graphical object (along with performing all of it’s side effects) the requirement to use the common high level delete implementation can get obscured. Especially when the various types of consumers haven’t been anticipated in the design of the model’s API. I’ve found the murkiness (and naughtiness) increases when a generalized data model framework is being used, as it invites direct access to manipulating the data model via it’s APIs. Such is definitely the case with Core Data.
In the past, my pattern muscle memory has had me implement intermediate model layers that encode the side effects, but it has always seemed like such a waste of time and effort to obfuscate direct access to the generalized data model framework APIs with intermediate layers.
In my Cocoa implementation, I choose to implement my data model side effects in KVO observers. I did this so that deleting my graphical element using the Core Data API results in the requisite side effects and dependent data model changes. The main reason I did this is to avoid requiring special discipline or knowledge of the side effects on the part of the consumer of the model. My KVO observers however, also implement the undo/redo registration as I touched upon above. The problem with this approach when naively done is the exponential accumulation of invocations in the undo and redo queues. Each KVO notification from successive undo and redo operations results in performing the side effects and dependent operations anew (in addition to the queued undo/redo invocation).
This is easily rectified by checking to see if our KVO notification is happening as a result of an undo/redo operation (i.e. [NSUndoManager isUndoing] or [NSUndoManager isRedoing]) before performing side effects and dependent operations. If we are in the midst of an undo or redo, we let the invocations on the queue take care of the side effects and dependent operations, but don’t ourselves reissue them as a result of the KVO notification.
The bottom line result is a clear and compact model implementation that is easier to implement, extend and maintain over time, one that does not constrain it’s consumers to specialized APIs or require specialized knowledge.
Well, at least that is the theory… Time will tell, but for now I am love-hating my pattern muscle memory disruption.