Jump to: navigation, search

OTPattern/AssociateByNesting

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;
        }
    }
}
  1. When the control flow enters the focus area at Base1.m1() this triggers enter().
  2. For the duration of enter() including its base call (i.e., the actual m1() execution) the current instance of Context is temporarily activated (for this thread).
  3. When Basez.mz() finishes, the callin to perform() will automatically create an Action instance as a nested instance within the exact Context instance from step (2).
  4. Inside perform() the reference to the enclosing Context is implicitly available (Context.this) and using an appropriate callout binding otherMethod() can be invoked on the original Base1 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