Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
OTPattern/AssociateByNesting
Contents
Intent
For the sake of a particular scenario two objects need to exchange messages, but neither has a reference to the other.
Motivation
A particular scenario finds that one objects needs another object to perform its operation, but no reference is at hand when needed. Also the object's class cannot or should not be extended with another field, because this reference is not part of the core concern of that class. It is then tempting to dynamically create associative maps that help the object needing the service to navigate to the object providing the service. However, manually maintaining such maps is error-prone boiler plate code.
This pattern helps if, instead of by an explicit reference chain the two objects are connected by a control flow of any depth (In this case it might be tempting to augment method signatures for explicitly passing one object down to the other. This will, however, not work for long call-chains where at least one method exists that cannot or should not be augmented in this way for whatever reasons).
Structure
This pattern maps control flow nesting to class nesting (team and role).
Consider the following base classes with an indirect control flow from Base1.m1()
to Basez.mz()
:
public class Base1 { public void m1() { new Base2().m2(); } public void otherMethod() {/* Basez will want to call this method */} } public class Base2 { public void m2() { new Basez().mz(); // the call chain could involve yet more objects and methods } } public class Basez { public void mz() { /* at this point we need to talk to our Base1 */ } }
This pattern superimposes a nested team:
public team class AssociateByNesting { protected team class Context playedBy Base1 { protected class Action playedBy Basez { ... } } }
Note, that Base2 requires no role because we abstract from the intermediate steps in the call chain.
Participants
The outer team AssociateByNesting only sets the stage so that the inner Context is a role and can receive callin triggers. This team Context maps to the point where the control flow enters the focus of the considered situation. Inside the Context, a role Action represents the point where communication between Base1 and Basez is required.
Collaboration
The collaboration is implemented like this:
public team class AssociateByNesting { // we assume that an instance of AssociateByNesting is globally active protected team class Context playedBy Base1 { void otherMethod() -> void otherMethod(); callin void enter() { within(this) base.enter(); } enter <- m1; protected class Action playedBy Basez { void perform() { Context.this.otherMethod(); } perform <- after mz; } } }
- When the control flow enters the focus area at
Base1.m1()
this triggersenter()
. - For the duration of
enter()
including its base call (i.e., the actual m1() execution) the current instance ofContext
is temporarily activated (for this thread). - When
Basez.mz()
finishes, the callin toperform()
will automatically create anAction
instance as a nested instance within the exactContext
instance from step (2). - Inside
perform()
the reference to the enclosingContext
is implicitly available (Context.this
) and using an appropriate callout bindingotherMethod()
can be invoked on the originalBase1
instance through which we entered the scenario.
Variants
If the control flow spawns additional threads the above within()
statement is not sufficient to capture the exectution of mz()
which may then happen in a different thread. In that case one might consider using activate(ALL_THREADS)
(plus deactivate) instead of within()
. In that case, care must be taken that no two instances of Context
can be active at the same time.
Known Uses
This pattern was made explicit when refactoring the team SamplesAdapter
from the org.eclipse.objectteams.otdt.samples
plugin (source in SVN). The old implementation maintained a map
private Map<IConfigurationElement,SampleWizardAdapter> _wizards = new HashMap<IConfigurationElement, SampleWizardAdapter>();
Now any object holding a reference to an IConfigurationElement
was able to obtain a reference to the role SampleWizardAdapter
that was registered to this key.
This implementation was then refactored to use the AssociateByNesting pattern. Thus the shown map could be removed and the arbitrary use of an IConfigurationElement
as the key was no longer needed. The required reference is now automatically present at the point where its needed. Thus even a null check could be eliminated (which was required after accessing the old map), because the nesting structure guarantees that the enclosing instance is always available.
Related Patterns
This pattern is similar to PerThreadRole