Dynamic Behaviors in Java

Dynamically adapt program behavior at runtime

1 2 3 Page 2
Page 2 of 3

If a behavior object cannot extend Behavior due to a requirement to extend another base class, the behavior object may instead implement the interface IBehavior. Each IBehavior method has trivial implementations that involve calling a corresponding static method in the Behavior class. Consult the source file Behavior.java and IBehavior.java if you need to do this.

Dynamically change application behavior

Once you have class methods with behavior dispatch code and behavior objects to use with them, dynamically changing an application's behavior is easy. You only need to define public access methods for your class's behavior list, as shown in the code below.

Listing 4. Dynamically change application behavior

 class DynamiclyExtendableClass
{
   BehaviorList fBehaviorList;
   public void addBehavior(Object behavior)
   {
      fBehaviorList.add(behavior);
   }
   public void removeBehavior(Object behavior)
   {
      fBehaviorList.remove(behavior);
   }
}

Other code in your application that wishes to change a given object's behavior may create behaviors and use the provided addBehavior()/removeBehavior() methods as needed. If the behavior objects used extend the Behavior class, then other capabilities are also available, as described in the following section.

Special capabilities of behavior objects

Full-class behavior objects (those that extend the Behavior class and/or implement the ActiveSubBehaviors interface) have special capabilities that clients may rely on. These capabilities include:

  • The ability to find the behavior's parent object
  • The ability to remove the behavior from all of the behavior's parent objects
  • The ability to treat sub-behaviors attached to this behavior as if they were attached to the behavior's parent object (for each parent individually) without actually inserting them in each of the parent's behavior lists

Access parent objects

Behaviors can be attached to multiple objects. The behavior method has access to all of the parent objects it is attached to, which can prove useful when employing behavior objects to associate two objects dynamically bound together for some reason.

In the current API, it is possible to request the first parent of a given class via the parentOfClass() method. A general iterator that visits each parent would be useful, but is not yet available. Note that the parent list is maintained as an ArrayList of weak references; clients that access the array list directly should be prepared for null references to appear in the list.

Remove behaviors from parents

Behaviors can be removed from their parent list in the same way that objects are usually removed from lists, via a direct call to the BehaviorList's remove method. In addition, behavior objects can be immediately removed from all lists they belong to by calling the behavior object's removeFromAll() method. removeFromAll() iterates all of the parent lists and removes the behavior. This capability is extremely useful for implementing duration-based enchantments; when the timekeeper determines the enchantment has ended, it need only call removeFromAll() on the behavior object, and all of the objects that the behavior modifies will return to normal.

Active sub-behaviors

A behavior can declare its sub-behaviors to be active—that is, to be seen by any object this behavior is added to—by implementing the ActiveSubBehaviors interface. Listing 5 shows an example of a behavior class that supports active sub-behaviors:

Listing 5. Active sub-behaviors

 class DoublingBehavior extends Behavior implements FooInterface, ActiveSubBehaviors
{
   ...
   BehaviorList fMySubBehaviorList;
   public Iterator inheritedSubBehaviors(Class behaviorClass, Object terminatingObject)
   {
      return fMySubBehaviorList.getInheritanceChain(behaviorClass, terminatingObject);
   }
}

When the behavior chain iterator sees that a behavior object implements ActiveSubBehaviors, it calls the inheritedSubBehaviors() method to request an iterator over the sub-behaviors to include. The implementation of inheritedSubBehaviors() should always appear exactly as shown in Listing 5.

Other considerations

Behaviors introduce other considerations into an application's design. Special care must be taken when cloning objects with behaviors, as adding behaviors to multiple parents creates a graph structure that cannot be cloned by the normal techniques. Also, the numerous classes a behavior-using application may acquire could add a lot of overhead to the work required to serialize the application's objects.

Thread safety

Note also that a behavior object can be attached to multiple dynamic objects. If this is done, in a multithreaded environment, take care when modifying the behavior's state—it must be changed safely. Where possible, immutable behavior objects should be used; however, one of the advantages of POJO (plain-old-Java-object) behavior objects is that they can contain state relevant to the application functions they affect. If using behaviors in a multithreaded environment, plan ahead, considering ways that multithreaded access may affect your design.

In addition to the behavior objects' internal state, the behavior list itself is another object that might be affected in a multithreaded environment. Behavior objects have a convenient removeFromAll() method that removes them from all behavior chains they have been added to. In a typical behavior-based application, most likely, at some point, a behavior will be removed from its behavior lists while active iterators on those lists remain. Typical Java implementations of thread-safe lists use a "fails-fast" technique to invalidate all iterators when a list's state changes.

Behavior lists take a different approach and instead fix any iterator affected by a change to the list's contents. Behaviors are by definition implemented as patches that can be applied and removed to executing programs. So usually, no ill effect should result from removing one behavior or another in the middle of a call chain.

One example where it might cause a problem, though, would be if a single behavior modified two methods, and the implementation of the first patch took action it assumed would definitely be balanced by the call to the second patch. This assumption might be violated if the behavior was abruptly removed from between the two method calls. It is best to avoid such dependencies, but when that is not possible, a behavior object may override the notifyRemoved() method and test to see if any fix is needed. BehaviorList provides a method called hasIterator() that you can use to determine whether the current removal is affecting your iteration.

Listing 6 demonstrates how a behavior object might detect the situation where it is removed unexpectedly. The assumption here is that methodTwo() is always called after methodOne(), and our example behavior takes advantage of that assumption to perform some cleanup.

Listing 6. Detect behavior removal and apply fixup

 class ImprudentBehavior extends Behavior implements ExampleBehaviorInterface
{
   Iterator fIteratorInProgress;
        
   public void methodOne(Iterator chain, int value)
   {
      // Do something imprudent and remember which behavior chain
      // we did it in
      fIteratorInProgress = chain;
      ((ExampleBehaviorInterface) chain.next() ).methodOne(chain, value);
   }
   public void methodTwo(Iterator chain)
   {
      this.fixupImprudentBehavior();
      ((ExampleBehaviorInterface) chain.next() ).methodTwo(chain);
   }
   public void notifyRemoved(BehaviorList removedFromList)
   {
      if(removedFromList.hasIterator(fIteratorInProgress))
      {
         this.fixupImprudentBehavior();
      }
      super.notifyRemoved(removedFromList);
   }
   protected void fixupImprudentBehavior()
   {
      // Undo our stateful action
      fIteratorInProgress = null;
   }
}

You may find it ironic that the example given in the section on thread safety is not itself thread-safe (since fIteratorInProgress will be overwritten on recursive and/or multithreaded calls to methodOne). I deliberately wrote the example that way to keep it as short and clear as possible. I leave it up to you to extend the technique to handle multiple threads—a task that will hopefully encourage you to find an atomic way to solve the same problem without requiring a call to a fixup method.

Note that I have never needed a behavior that sets and restores state as shown in Listing 6, so the code shown in that example is for reference only; it is completely untested.

Clone a graph structure

When behavior objects are attached to multiple objects in a tree-shaped hierarchy, the resulting structure is a graph. It is not possible to clone a graph using Java's clonable mechanism; therefore, another technique is required. Even if using clonable were possible, it is not necessarily practical. Applications that use behaviors typically define numerous small classes, many of which contain just one method only a few lines long. The tedium of adding implements clonable and overriding the clone method could become burdensome and error-prone over time. A better approach is needed.

Vladimir Roubtsov's JavaWorld article "Attack of the Clones" (January 2003) compares different techniques for object cloning and presents their performance differences. The ReflectiveClone class he describes solves our problem nicely, duplicating an object graph and fixing internal references with acceptable performance. A derivative implementation is included in this article's package (republished with permission), modified to fix weak references inside a subtree, but not follow them. The parent links maintained by behavior objects are stored via weak references. This allows a subtree of a complex object graph to be cloned without taking the entire universe of reachable objects with it.

Note that the reflective clone algorithm cannot clone objects of classes that are instances of inner classes. (This restriction does not apply to static inner classes, only fully inner classes that contain implicit references to their parent objects.) Therefore, behaviors should not be implemented with inner classes.

Serialize Dynamic Behaviors

Serializing graphs is possible, so object hierarchies that use behaviors can be serialized and transmitted using standard Java techniques. However, additional considerations to serialization might make it a bit too heavyweight for use in an application that extensively uses behaviors. Joshua Bloch's book Effective Java (Addison-Wesley Professional, June 2001) covers this topic in items 54 through 57—important reading for anyone considering using serialization in a complex application containing many classes.

One possible alternative to the serialization class is JAXB (Java Architecture for XML Binding), a component of the Java Web Services Developer Pack that serializes and deserializes object hierarchies to and from XML given an XML schema. However, JAXB handles only hierarchy trees, not graphs. I solved this problem by giving behaviors a primary parent that holds the behavior's serialized form and keeps references to the other objects it modifies. After unmarshalling, I call a fixup method that reattaches the behaviors to its other parents.

A full discussion of serialization techniques reaches beyond this article's scope. Consider carefully the available options before moving too far into the system's implementation, as changing serialization strategies can greatly affect an application's design.

Comparison to Aspect-Oriented Programming

As already mentioned, the techniques described in this article are nothing new; they date back to MacApp 3.0, circa the early 1990s, and have roots in SmallTalk before that. The only new thing presented here is a specific implementation of the design pattern that makes it easy to quickly implement many distinct behavior objects with unique method signatures.

Aspect-Oriented Programming (AOP) is a more recent invention receiving a lot of press lately. It is similar enough to Dynamic Behaviors that a comparison is useful.

If you are unfamiliar with AOP, you might want to read the tutorial listed in Resources. The difficulty in comparing Dynamic Behaviors with AOP is that there are multiple implementations of the AOP paradigm, but no standard syntax or single set of features. In short, the two technologies are similar in that they both provide a mechanism for patching code at the beginning and end of class methods. The two technologies also have many differences.

Most AOP implementations allow point-cuts (patches) to occur globally across all object instances of class methods, whereas dynamic behaviors are inserted on an instance-by-instance basis (that is, you must add a behavior object to every instance of a given class you wish to modify). However, one advantage that AOP offers is the ability to define a single aspect that point-cuts many methods. This approach is often done in an XML configuration file via a regular expression description of the method names that should be intercepted.

Dynamic behaviors, on the other hand, modify individual instances of objects. To affect a change in behavior, you must attach a modifying object directly to the instance you wish to patch. If you consider enhancing this Dynamic Behaviors implementation so that it also provides global behaviors (for example, by adding static behavior lists, dispatching through singleton objects at the head of method calls, and so on), you might instead want to investigate AOP and leverage the available tools already supporting such functionality. For applications that fit into the usage model of Dynamic Behaviors, though, this design pattern is a simpler option that is easier to adopt and deploy.

Conclusion

1 2 3 Page 2
Page 2 of 3