next up previous
Next: Organization Up: Introduction Previous: Issues Involved in

Safely and Efficiently Supporting Behavioral Evolution

The essence of the strategy pattern is to allow the behavior of an object to vary dynamically, while maintaining the same interface for the object. As there does not exist a direct mechanism in a traditional class-based language or model for supporting multiple implementations of a method for an object, the translation of the strategy pattern into a corresponding class-based model, as in Figure 1, turns the relation into aggregation and inheritance.

To achieve behavioral evolution we introduce a new kind of relation called a context relation to the object-oriented model [21]. The relation exists between classes and is intended to support behavioral evolution while maintaining the safety and performance benefits available with strongly typed, class-based languages. The context relation produces class hierarchies orthogonal to inheritance hierarchies, in many cases replacing inheritance. The relation is meaningful at the design and implementation level. The basic idea is that if class C is context-related to a base class B, then the C class implements one or more methods of the B class. A C-object may be attached to a B-object to alter the behavior of the B-object, or a C-object may be attached to a method invocation to alter the behavior of a group of B-objects for the duration of some task. The context relation supports dynamic behavior because the C-object related to a B-object can vary at run-time.

Figure 3: Context relation

In this paper, we focus on two forms of behavior evolution:

Figure 3 contains an object model that will be used to demonstrate the two forms of behavioral evolution. The figure shows the relation between a base class called Base and two context classes ContextM and ContextN. The context relation is drawn as an arrow between classes, using the C++ scope operator to emphasize its role of altering the base class implementation. A context class does not inherit methods from a base class, and is not considered a subclass of a base class. Rather, a context class can define methods for its own class instances, such as the f method of the ContextM class, as well as define method implementations for base class instances, such as the m method of the Base class that is redefined in the ContextM class. A context class may have multiple direct base classes. Likewise, a base class may have multiple context-related classes, each altering one or more methods of the base class. Several context classes may redefine the same method of a base class.

Table 2: Method Types

Recall that we want to support two forms of behavioral evolution, altering either the implementation of an object or the implementation of a class. Languages such as C++ differentiate the attributes (data members) defined for a class as being either instance variables or class (static) variables. To achieve the two forms of behavioral evolution, we differentiate between methods whose implementation should vary on a per object basis (referred to as instance-stored methods), and those that will vary for the class as a whole (referred to as class-stored methods). Figure 2 compares the C++ terminology of class and instance methods to the new terminology we propose for categorizing methods.

The Base class in Figure 3 defines two attributes x and y. The x attribute is an instance variable, implying each Base class instance will have memory allocated to store the attribute. The y attribute is a static class variable, implying all class instances refer to the same memory location for this attribute. The Base class also has two methods, m and n. The m method is an instance-stored method, implying each Base class instance will have memory allocated to store the address of the m implementation. This allows the implementation of m to vary per object. The n method is a class-stored method whose implementation may vary dynamically, however all class instances will refer to the same implementation since the implementation is stored with the class rather than individual class instances.

In static languages like C++, the method implementation invoked when an object receives a message is either (1) the implementation defined for the class of the variable used to invoke the message, or it is (2) the implementation defined for the class of the object receiving the message, as in the case of a virtual function and pointer reference. In the simple case of non-virtual functions, instantiating a class and sending messages to its instances will cause the method implementations defined in the class to be invoked (or those inherited from a superclass). Even with virtual functions, each class still has only one implementation per method interface, thus the virtual function tables are static. Each object conceptually has one context defining its run-time behavior, namely its static class implementation.

Figure 4: Dynamic method lookup

However, we want to support more dynamic forms of behavior than the traditional static class model allows. This is easily achieved using the context relation between classes. Figure 4 shows object b, an instance of the Base class from Figure 3, receiving messages. An m message will invoke the m implementation that is stored with the object. An n message will invoke the implementation stored with the dynamic class Base. The classes ContextM and ContextN are context-related to class Base, and define alternative implementations for the Base class methods. Instantiating class ContextM produces a context object, which may be used to alter instances of class Base dynamically. For example, attaching the c1 context object to the Base object b will alter the m method implementation to be that which is defined in the ContextM class. Instantiating the ContextN class and attaching the c2 context object to a method invocation will alter the Base class implementation of method n for the duration of the call.

Figure 5: Strategy pattern using context relations

Figure 5 describes how to model the strategy pattern using context relations. The context relation has neither an analogous abstraction in existing object models, nor an analogous program construct in existing languages. Thus, it has traditionally been implemented using existing relations such as aggregation and inheritance, with the essence of the strategy pattern lost in both the object design and the translation to code. To remedy this, the context relation is used to link the Receiver class to its alternative method implementations, as shown in Figure 5. The Receiver class describes the method interfaces for its instances, yet the implementations are given separately in the strategy hierarchy defined by the context relation. Thus, the Receiver will define the structure and interface of its instances, namely that there is a method m, while the alternative implementations of m are given in the strategy classes, one of which is designated as a default implementation. When the Receiver class is instantiated, the object will have the default m method implementation initially defined for it. Separating the implementations of m from the Receiver class allows dynamic variation of the method. Ensuring that one of the implementations is declared as a default will protect against a ``message not understood'' error. The context relation may be labeled with the name of the method being redefined to facilitate diagram understanding. Notice the abstract class Strategy no longer exists, as was required in Figure 2.

Figure 6: Updated object model for Composition

An updated model of the line-breaking strategy class design is shown in Figure 6, with the context relation used to link the Composition class and its alternative method implementations.

next up previous
Next: Organization Up: Introduction Previous: Issues Involved in

Karl Lieberherr
Tue Jan 21 09:24:10 EST 1997