This short article is also available as postscript or PDF.
Centre de Recherche Informatique de Montréal
550 Sherbrooke West, suite 100
Montréal (Québec) Canada H3A 1B9
+1 514 840 1234
The RoleAdapter Design Pattern allows using objects of any model as if they implemented any given programmatic interface, with contextual behavior. To achieve this, it makes objects from many basic building blocks of OOP, like methods, method signatures, interfaces, etc. This allows clients of a data model to define, at run-time, an interface for any data model they have to use. Objects encapsulating methods, defined independently for the model, are chosen and bound to the signatures included in the interface according to external configuration hints. Since the adaptation is done local to a context, different view instances can show different aspects of a complex model. The resulting composite definition of interface is similar in intent to that of subject-oriented programming, but achieved wholly within a traditional OOPL like Java.
Design Patterns, Roles, Subject-Oriented Programming
The RoleAdapter pattern allows us to use objects of any model as if they implemented any given programmatic interface, with contextual behavior. Traditional OO design emphasizes that the data model should be generic enough to be used with many views. The views, on the other hand, must reflect the model's structure in their display, and hence are more closely tied to one model's structure. We cannot normally reuse view components with different models, unless they share an interface. In CASE tools, each node in a class diagram is involved in both an inheritance and an aggregation hierarchy. We would want to display the hierarchy obtained from these distinct interfaces in the same reusable display tool.
Ideally, each view we use should contribute to the model's interface at run-time, as in Subject-Oriented Programming . Within conventional OO languages, many Design Patterns are aimed at altering a frozen object's interface. The original Adapter  does this, but only at compile-time, and it does not scale well; in a complex system of objects, one adapter is built for each object of the original system, relationships between adapted objects have to be translated into relationships between adapters, etc. Other relevant Patterns are Erich Gamma's Extension  pattern, Baümer and Riehle's Role objects , etc. Unfortunately, those patterns require the object to follow some extension protocol, which we wanted to avoid.
RoleAdapters are defined on arbitrary classes (not instances) at run-time. To that end, many basic constructs of OOP were made into first-class objects: the method implementation; its signature (name, return and parameter types); the role (or interface: the set of signatures that a class must implement when used in a certain role by a client), etc. (cf. diagram.)
The client expects to use objects according to a set of
Signatures defined in a
Signatures will correspond to
Binding objects, grouped in
RoleAdapters, will contain the corresponding pairs. Initially,
Bindings only contain
Signatures; the unbound
RoleAdapter is then equivalent to the
Role. As a client attempts to make an instance of a certain class play a certain
Role, it asks the
Context to create a valid Class-
RoleAdapter relationship (
Casting) by fulfilling each of the
Bindings with a
Method that satisfies the
In order to find appropriate
Context owns a distinctive set of
Hints, containing factories that will attempt to build the
Method from its
Signature (if the required
Signature fits that of the
Hint.) Configuring different views'
Contexts with different
Hints allows for fine-grained simultaneous access to various aspects of a model at run-time. The
MethodFactory's job may be simple, like creating a
Method of a given class, or retrieving an existing
Method with an alternate
Signature from the
Context, but more complex
MethodFactories allow us to specify arbitrarily complex derived
Hint specifies how to build our
Context queries the context-independent
MethodRegistry for a model-specific
Method that adequately fulfills the
Binding. The set of those model-specific
Methods amounts to a dispersed adapter for the model. The candidate
Casting, and any
Casting registered previously, establish equivalence classes between the
Signatures of those
Methods defined strictly in terms of classes, and more abstract
Signatures owned by, and defined in terms of
Roles. In case of failure, the
Context asks a broader
Context that may have further
Hints (using Chain of Responsibility.)
After the binding phase, the client can access the
Method through the
RoleAdapter as follows:
instead of, traditionally:
The latter code is also what will often be found in the
execute method. But the flexibility we gain offsets the cost of doubling all virtual calls.
Methods as first-class objects allows us to build and define complex structures from them. For example, we can compose links between objects through Object Composition on accessor-like
Methods. Similarly, we have found it useful to define some setter-like
Methods to be observable, through wrapping them in an observable Decorator. We also use the Observer pattern to create dynamic
Methods, from a
Binding that observes the
Context for changes in the
Hint definitions. In all cases, these complex
Methods are used interchangeably with the atomic ones. We believe that these objects are an interesting Design Pattern in their own right, inspired by Strategies and reminiscent of ValueModels .
Using this pattern, we believe that we have achieved a sizeable fraction of the advantages of subject-oriented programming within the confines of a traditional object-oriented language. Readers interested in the uses of the
RoleAdapter pattern should refer to papers on the Giza framework .
The author wishes to thank CRIM for its support of our team's research in information visualization. Many thanks go to Luc Beaudoin, who made Giza necessary by inventing innovative visualization techniques. The project to apply them to diverse data models received support from Frances de Verteuil, director of the HCI group at CRIM. Many people at CRIM contributed to the Giza architecture in various ways, most notably Louis Vroomen who made it yield visible results long before it should have. Ruedi Keller, of Université de Montréal, also offered useful input.