Intention
The intention of the PropObj is to provide a generic interface to store and/or retrieve data without the need to understand the internal workings of the object. Implementing this can result in a great deal of repeated code or boilerplate code if the object itself structures itself into other PropObjs from which the interface should include.
The PropDef exposure pattern exists to answer this problem. Exposing PropDefs involves a PropObj (henceforth referred to as the 'outer' object) replicating the PropDefs of another PropObj (henceforth referred to as the 'inner' object) onto itself. The new PropDefs will be "thin" PropDefs that redirect their entire interface back to the PropDefs that they were created from. As a result, the outer object has its interface expanded to include the capability of the inner object.
As an added benefit, because the calls to these props are still going through the outer object, it has the opportunity to intercept these calls if needed. So, the outer object may tweak the behavior to suit its use-case.
The Steps
Before getting started with PropDef exposure, it's important to first consider how the outer object references the inner object. In order for the exposure to work, the outer object must be able to yield the inner object via a call to get()
. The details behind this are unimportant; it does not matter if there is a PropDef that yields the inner object or if a reference to the inner object is stored in anonymous data, so long as the inner object is returned from get()
with a specific key.
PropDef exposure depends on a key called a pipekey. The pipekey is the prop key that retrieves the inner object. If the developer is not expressly providing a PropDef for the inner object, the pipekey also indicates by which key the inner object should be stored in anonymous data. Additionally, all of the PropDefs that are generated by the exposure will have this pipekey stored on them, so they know where they are redirecting their calls.
Register the Pipekey
public void outerObjectInit() { InnerObj inner(); put(pipeKey, inner); ownerRegisterExposure(pipeKey); }
Note that the above example works with the inner object being stored in its anonymous data.
OR this would look similar if the object were instead available through a PropDef :
public class OuterObj extends PropObj { public InnerObj innerObj : copy=reference; public props { "innerObj"; } public constructor(PropObj inner) { innerObj = inner; ownerRegisterExposure("innerObj"); } }
(Re)Expose the PropDefs
ownerRegisterExposure()
alone does not trigger PropDef exposure. Instead, it adds the pipekey to a list that will be exposed when ownerReExposePropDefs()
is called, as shown below:
public void ensureSettings() { super(..); ownerReExposePropDefs(); }
Under the Hood
What Exposing Does
The actual exposure of PropDef is carried out by the function exposePropDefs()
and the PropDef visitor, ExposePropDefsVisitor
. The specifics of what the visitor does in accept()
varies slightly based on the arguments passed to exposePropDefs()
, but generally it will
- Create a PropDef with the same attributes as the PropDef it is exposing
- Assign the PropDef the pipekey
- Add the PropDef to the PropDefs on the outer object
After all of the PropDef have been visited/exposed, the visitor will then add the pipekey to exposedFrom
on the PropDefs of the outer object, and exposedTo
on the PropDefs of the inner object. exposedFrom
and exposedTo
act as a kind of history of exposure. This is how the objects know how to re-expose the PropDefs again at a later time; that stored pipekey is enough information for exposePropDefs()
.
ownerRegisterExposure()
and ownerReExposePropDefs()
take advantage of exposedFrom
to make prop exposure easy. ownerRegisterExposure()
will add the pipekey to the exposedFrom
set on the PropDefs of the outer object, making it appear internally as if the props were already exposed (even though they likely were not). ownerReExposePropDefs()
will iterate through the pipekeys in exposedFrom
, and invoke exposePropDefs()
for each one. Note that this requires the inner object(s) to already be available via get()
; it will not be able to automatically add inner objects to anonymous data the way exposePropDefs()
can when called directly.
How Exposure/Piping Works
Once the 'piping' PropDefs are in place on the outer object, it is their responsibility to perform the actual piping behavior. All of the PropObj /PropDef interfaces will pipe down to the inner object when a pipekey is present. This is accomplished by first retrieving the inner object by invoking get()
with the pipekey as the key. Then, the "target" PropDef is retrieved, and the appropriate method is invoked on the PropDef.
Recursion and Nested Objects
The functions described above will also work for more complex object hierarchies/relationships. Consider three objects: A, with inner object B, which itself has an inner object C. It is possible for the PropDefs on object C to be "chain" exposed upward through B and onto A. When organized this way, props that have been exposed up from C will first be called on the object A, then piped down to object B, and finally down to object C.
This is easy to accomplish, but the exposure must happen in the correct order. C's props must first be exposed to B, and then B's props can be exposed to object A. ownerReExposePropDefs()
can take care of this order automatically. The optional argument recursive
will cause the function to, depth-first, invoke exposePropDefs()
.
Exposure and Properties
PropDef exposure integrates well with properties, as the only real requirement for properties is PropDefs . Most of the property calls will automatically pipe down to the correct owner of an exposed PropDef . The exceptions to this are methods that are introduced by the CoreObject class. Those method calls will need to be manually passed to inner objects by the developer. These methods include:
manipulateInputSetting()
orderProperties()
orderPropertyGroups()
propertyGroupLabel()
manipulateObjectToLabel()
Comments
0 comments
Please sign in to leave a comment.