Genericity and Variability: A Framework for Graphical Editors

Hannes Schmidt
hschmidt@cs.tu-berlin.de
Technical University Berlin

October 02, 2001


Abstract

The reuse of software is one of the main goals in the field of software engineering. The principle of inheritance between object classes in object-oriented languages was intended to enable software engineers to reuse their software. However, during the last decade, it has frequently been questioned whether inheritance really works as it was intended to, whether systems designed using inheritance can really be extended and maintained easily, and whether the parts of these systems can be used in new systems.

The relatively young discipline of Generative Programming criticizes the usual OO-methodology for its primary focus on the production of a single system. It also states that creating reusable software requires a different approach. If the design is aimed towards one particular system out of a family of systems it is unlikely that the resulting implementation will be usable for other systems in that family.

Instead, software engineers who are interested in the reusability of their products should focus on a design which is targeted to a family of systems. In this paper, I will validate this new approach by designing and implementing a generic application framework for graphical editors of visual languages.

Chapter 1 gives an overview of selected GUI frameworks/toolkits, examines their design and analyzes the concepts behind them. It shows the advantages and disadvantages of those systems from the client programmer’s standpoint as well as from the system maintainer’s standpoint.

Chapter 2 analyzes various alternative design methodologies and implementation techniques. It shows how these concepts can be used to improve the flexibility and reusability of object oriented software in general and application frameworks in particular. The chapter ends with the description of WeaveJ, a tool which implements some of the ideas and concepts introduced in this chapter.

Chapter 3 describes the design and implementation of a generic, reusable and flexible application framework for graphical user interfaces which can be used to implement graphical editors of visual languages. The focus of this chapter lies on the implementation of an object-oriented constraint solver which is used to layout the components.

Contents

Abstract
1 State of the Art
 1.1 Java : AWT and Swing
  1.1.1 The Design of AWT
  1.1.2 The Design of Swing
  1.1.3 The Observer Pattern in Swing
 1.2 Inheritance: A Real World Example
  1.2.1 What is Inheritance?
  1.2.2 Inheritance: A Real World Example
 1.3 C++: Unidraw
  1.3.1 Weaving Design Patterns
  1.3.2 Layout
  1.3.3 Commands
 1.4 C++: InterViews
  1.4.1 The Flyweight Pattern
  1.4.2 Styles
  1.4.3 Factory Methods
  1.4.4 Layout
 1.5 ET++
  1.5.1 Bottleneck Interfaces
  1.5.2 Screen Update
 1.6 SELF: OOP without Classes
  1.6.1 Language Concepts
  1.6.2 Composition and Delegation
  1.6.3 Conclusions
2 Alternative Techniques
 2.1 Weaving Design Patterns
  2.1.1 Using Delegation to Weave Patterns
  2.1.2 Object Schizophrenia
  2.1.3 External Delegation
  2.1.4 Conclusions
 2.2 Generative Programming
  2.2.1 Domain Engineering
  2.2.2 Feature Modelling
  2.2.3 Domain specific languages
  2.2.4 Feature Modeling vs. Design Patterns
 2.3 C++: Static Metaprogramming
  2.3.1 The Storage-Space vs. Running-Time Tradeoff
  2.3.2 Templates
  2.3.3 Parameterized Inheritance
  2.3.4 Code-Bloat
  2.3.5 The GenVoca Architecture
 2.4 GJ: Bounded Parametric Polymorphism
  2.4.1 Using GJ
  2.4.2 GJ’s Type Algorithm
  2.4.3 Bounded Parametric Polymorphism
  2.4.4 Conclusions
 2.5 Aspect Oriented Programming
  2.5.1 Separation of Concerns
  2.5.2 Aspects
  2.5.3 AspectJ
  2.5.4 Conclusions
 2.6 WeaveJ
  2.6.1 The WeaveJ Development Process
  2.6.2 Serialization
  2.6.3 Duplication
  2.6.4 Supporting Inheritance
  2.6.5 Binding
  2.6.6 Object Creation
  2.6.7 Deployment
  2.6.8 Implementation Issues
  2.6.9 Further Development
  2.6.10 Changes
3 The JDraw Framework
 3.1 Overview
 3.2 Architecture
  3.2.1 Motivation
  3.2.2 The Subsystems
  3.2.3 Built-in Components
 3.3 The Layout Subsystem
  3.3.1 Rulers and Links
  3.3.2 Solving the Network
  3.3.3 Implementation Issues
  3.3.4 The Box Layer
  3.3.5 Conclusions
 3.4 The Appearance Subsystem
  3.4.1 The Interfaces
  3.4.2 Z-order
  3.4.3 GlyphBox
  3.4.4 The World
  3.4.5 Viewport
  3.4.6 Update handling
 3.5 The Consistency Subsystem
 3.6 The Properties Subsystem
 3.7 Further Development
Terminology
Zusammenfassung in deutscher Sprache
Bibliography

Chapter 1
State of the Art

In this chapter I will give an overview of a choice of different application frameworks and GUI toolkits implemented in different languages. Although being far from complete, this overview should enable the reader to understand the limitations of inheritance based approaches to framework design and implementation.

The topic of this paper are frameworks for graphical editors. This chapter covers both, GUI toolkits and graphical application frameworks, because they are very similar. They are both centered around the idea of visual objects, i.e. components1. They differ in their scope and in the way they influence the application’s architecture. The scope of an application frameworks is to take care of most if not every aspect of an application. The scope of a GUI toolkit is just the components. From an architectural point of view, application frameworks are built upon toolkits. Due to their larger scope, application frameworks heavily affect the architecture of the applications.

1.1 Java: AWT and Swing

Java is an object-oriented language with a syntax similar to that of C and restricted and stream-lined object-oriented semantics. It features robust multithreading, dynamic binding and loading; has a built-in garbage collector; and supports persistence. The platform independence of Java programs was a major objective during Java’s language design. A compiled Java program should run without modification or re-compilation on any platform for which an implementation of the Java Virtual Machine (JVM) exists.2

Java is easy to understand and is free of the obstacles found, for example, in C++. It supports distributed computing (RMI, CORBA). Java programs are relatively small, which allows code to be downloaded on demand at runtime.

Java programs are an order of magnitude slower than their C or C++ counterparts. This is true even today, although new JVM implementations (HotSpot, [SUN]) have improved performance significantly. Java has also suffered from its poorly written first release runtime library (for examples see [Bec00]) and its lack of an open language standardization process. In the past, Sun addressed both issues with the Java Community Process and improvements to the runtime library, i.e. new container classes, JFC, etc.

1.1.1 The Design of AWT

When Java was initially released it came with a set of packages for common programming tasks. Among them were stream and file I/O, time and date calculation, data type conversion, character string manipulation, network socket communication, object persistence and data structures (container classes). It also came with a package for graphical user interface programming: Abstract Windowing Toolkit (AWT).

According to [Eck00] the first release of AWT was designed in only one month. Because of that it seems only logical that AWT would lack many of the features which developers of real-world applications find vital. One of the most serious limitations of the first release was that the developer needed to subclass AWT components in order to handle events generated for that component. This was soon fixed for the second release, which allowed developers to add event-handlers dynamically to component instances. Unfortunately, the second release had to be compatible with the first one, which meant that both event handling schemes had to coexist. This caused some confusion among programmers.3

It was one of AWT’s major design goals that it conform to the write-once-run-anywhere strategy of its implementation language. The designers realized that there were two different ways to enforce AWT’s platform independence. The first approach made use of existing standard components native to each platform. Taking the second approach meant implementing the major part of the components in Java itself and writing very little native, i.e. platform dependent, code to access the target operating systems graphical output subsystem.

The designers chose the first approach - a component implementation in Java and native code acting as a bridge to the so called peer, a platform specific component. An example of this is a simple push button. The button component consists of a class called Button, an abstract class called ButtonPeer, and ButtonPeer-derived classes containing mainly native methods - one derivative per platform.

This approach is advantageous, in that a user of a Java AWT application is faced with the natural GUI for his/her platform. On the other hand, this approach also has disadvantages; the main one being:

Beginning with JDK 1.1, AWT allowed for ‘peer-less’ aka light-weight components, that is components without a native peer. That enabled programmers and third-party vendors to write their own pure-Java components and mix them with heavy-weight AWT components.

1.1.2 The Design of Swing

Sun, becoming aware of the severe shortcomings in the design and implementation of AWT, attempted a fresh start. The result was the Swing toolkit, which addresses all the above mentioned drawbacks. Swing is part of Sun’s Java Foundation Classes (JFC) and its main advantages and disadvantages are as follows:

1.1.3 The Observer Pattern in Swing

Since I will repeatedly refer to the Observer pattern, it would be helpful to understand why this particular pattern is so important for GUI programming. Usually a user interface is a graphical representation of a programs internal data. The representation used may be for viewing only or it may allow the user to manipulate it and thereby manipulate the represented data. It is essential to establish a synchronization scheme between the representation and the data such that both remain consistent. Additionally, there may be different possible representations of the same internal data. In some cases multiple such representations need to be visible simultaneously.

All the above mentioned features recur in almost every program that has a graphical user interface. From recurring features and program behaviour a Design Pattern[GHJV95] can be extrapolated. The design pattern I describe here is commonly known as Observer.5

In Swing almost every component acts as a listener (Observer, in the standard pattern). To use this component, the client has to implement the corresponding model interface. The model declares methods that will be invoked by the component after the model has informed the component about changes in the data.

The JList component, for example, implements the ListDataListener. This interface contains methods which notify the list about certain changes in the underlying data. There is also the ListModel interface, which must be implemented by the client. This interface contains methods for registering and unregistering a listener (Attach() and Detach(), in the standard pattern), retrieving an element by its index and getting the total number of elements to be displayed.

Each component makes certain assumptions as to how the data it displays is structured. A text field requires a character string, a list requires a one-dimensional field of elements and a table requires a two-dimensional field of elements. It is therefore necessary that each component defines its own pair of model and listener.

The registration of listeners and their notification is something all models have to do. This suggests that application specific models should inherit from some toolkit/framework-specific parent class which implements the shared behaviour. Unfortunately, models in Swing are interfaces and as such cannot contain or inherit functionality. Therefore, Swing provides abstract helper classes, which are incomplete implementations of the model interface and which relieve the application programmer from the burden of managing the listeners. The application programmer can use the helper as the base for his/her own implementation of the model.

This approach becomes problematic when one application class wants to serve as a model for multiple different components. Dialog windows are examples of such classes. Swing application creates a dialog window by deriving Swings’s JDialog class. The components on the dialog are created during the initialization of the dialog subclass. If the dialog contains a list and a tree component, the dialog subclass would have to implement two different model interfaces: ListModel and TreeModel. The helper classes would be useless in this case, because helpers need to be extended and the application specific dialog class already extends JDialog.

Multiple inheritance would resolve the problem but Java does not have multiple inheritance. Instead, Java has inner classes. The dialog subclass of the above example can use non-static inner classes for each model. The inner model classes can extend a helper class and have access to their outer class’ members.

__________________________________________________________________________
Inner classes in Java are classes whose definition is nested inside another class definition. There are static inner classes whose purpose is mainly better code organization, i.e. keeping related things together and preventing name space pollution.6

Non-static inner classes additionally have access to all of their outer class members. To do so, every instance of a non-static inner class keeps a hidden reference to the outer class instance that created it. Furthermore, an inner class (static and non-static) can extend any class or implement any interface, completely independent of its outer class. Java’s non-static inner classes are an example of using object composition as an alternative to multiple inheritance. Instead of having the outer class C extend more than one base class (B0, B1, B2, ...Bn) one could have C create instances of inner classes Ii each of which extends one of the base classes Bi except B0 which is extended by C. Instead of handing out a reference to itself when a Bi sub-type is required, it hands out the reference to an instance of Ii.

Thus, the inner class objects have access to the outer class object and more than one base-class can be extended. The major disadvantage of this technique (and others using object composition) is object schizophrenia which will be examined in later sections.
__________________________________________________________________________

1.2 Inheritance: A Real World Example

In the world of object oriented programming, inheritance plays a major role and has often been claimed to be the main concept behind reusable software. Unfortunately, this claim has not been lived up to. In this section I will analyze why. According to [Boo94], software reuse can be achieved in different ways:

“Ultimately, software reuse can take on many forms: we can reuse individual lines of code, specific classes, or logically related societies of classes. Reusing individual lines of code is the simplest form of reuse. . . . We can do far better when using object oriented programming languages by taking existing classes and specializing or augmenting them through inheritance. We can achieve even greater leverage by reusing whole groups of classes organized into a framework.”

The first observation is that code reuse can be accomplished at different levels of granularity. Another observation is that the intent of reuse increases with the level of abstraction. A person writing a single line of code or a even a whole method does not intend for someone to copy it into a different project later on. It is much more likely that a person which designs a class, does so with the intention to enable someone else to reuse the class by inheriting from it. Finally, a framework is certainly designed to be reused. In this thesis I will focus on the reuse of single classes and the reuse of frameworks. Inheritance is probably the most widely used way of reuse on a per class level. This is why will start with an analysis of reuse through inheritance. The reuse of whole application frameworks follows in sections 1.3, 1.4 and 1.5.

1.2.1 What is Inheritance?

Probably the most common pitfall for programmers regarding inheritance is the lack of distinction between all the different effects it has on the inheriting class as well as the inherited one:

The problem with class inheritance is that if you need one effect you will automatically get the others as well.8 The following example will illustrate this.

1.2.2 Inheritance: A Real World Example

Imagine that you are an application developer in a small software consulting firm and you are assigned to a project for a larger company. Your part of the project is the front-end, which will be employed by its users everyday to enter large amounts of data. The front-end will comprise a dozen different forms, each containing 10 to 30 components - mostly text fields, push buttons and lists. Due to the amount of data, the users request that they be able to operate the whole front-end using keyboard keys only. For certain reasons the program is written in Java and since the GUI builder used in your company only supports AWT, you are forced to use AWT.

Unfortunately AWT does not fully support keyboard navigation in an intuitive manner. AWT adheres to standard GUI guidelines for keyboard input and therefore the navigation between fields is done using the tabulator key. The return key closes the form as if the OK button had been clicked. The users however, request be able to navigate between fields using the arrow keys and that hitting the return key advances to the next field. Because of this and other requirements you decide that you need to add some functionality to every AWT component class that you use.

Adding functionality to classes for which you do not have the source is generally done using inheritance: you inherit from the class to be extended and add the necessary functionality to the derived class. This approach becomes problematic when you need to add the same functionality to several classes. Provided that all inherit from a common base, one might be tempted to derive from their base. In your case the common base is java.awt.Component, the super-class of all the components on your forms. You can, of course, derive from java.awt.Component but you would also need to make the component sub-classes (lists, buttons and text-fields) inherit your new common base (figure 1.1). With Java this is not possible without modifying source code, which may not be an option because you simply do not have the sources or because you are not willing to re-apply the changes to every new release of AWT. Generally, it is virtually impossible to insert a Java class into the inheritance lattice.


PIC PIC

Figure 1.1: Modifying the inheritance lattice. The left hand class diagram shows the original inheritance tree, the right hand diagram shows the modified one.

Theoretically, making a Java class inherit a different super-class is possible by modifications on the byte-code level. The binding between a class and its super-class is rather weak (just a few entries in the class file’s constant pool). If the new super-class is a derivative of the old super-class the modifications should be transparent to all sub-classes and the old-super class. A language which supports dynamic inheritance (SELF) will be introduced in section 1.6.

The only option left is to derive from every component class which you use, namely Button, Textfield and Choice. The functionality that you initially wanted to be shared is going to be duplicated in all three derived classes.9 To lessen the impact of duplication, you decide to encapsulate that functionality in a separate class and to forward requests to an aggregated instance of this class from within the derived component classes.10

For other reasons you also have to add a minor feature to the text field class. (This example is based on a real world project and the true reason for the tweak is rather complicated. In fact, it is a work-around for a bug introduced by a third party extension to text fields). The text field class has a method called select() which marks the whole text in the text field component. What you need to do, is to fake the return value of the getText() method while select() is active. private int inSelect = 0;  
 
public String getText( ) {  
   // when select() asks for the text  
   if( inSelect > 0 ) {  
       return String.rtrim( getFormattedText( ) );  
   }  
}  
 
public synchronized void select( ) {  
    inSelect++;  
    // we know that super.select() will call getText()  
    super.select( );  
    inSelect--;  
} This work-around is only possible because of the tight coupling between super-class and sub-class. There is nothing bad about this particular work-around, other than that it may only work for the current release of AWT. For a work-around this is acceptable. What is harmful, is that those kinds of dependencies between derived and deriving class are possible and even encouraged by inheritance-based object oriented languages.

The intimate knowledge which a deriving class has about its super class breaks encapsulation and violates the hiding principle of software engineering. This is why inheritance is referred to as white-box reuse as opposed to black-box reuse [Szy99]. This hiding principle, separating interface from implementation, is a primary prerequisite for the creation and maintenance of reusable class libraries. Inheritance is often regarded as a way of reusing and customizing the classes in the library. In reality it makes the maintenance of libraries harder because it increases the likelihood that a change to a library class - while still in accordance with the interface contract of that class - breaks classes derived from the changed class. This problem is also referred to as the Fragile Base-Class Problem [Szy99]: modifying the base class might (1) break classes derived from it or (2) make it necessary to recompile the derived classes. In Java the second case can only be caused by modifications to the interface of a class.11

Most OO-languages offer the option to restrict the access from sub-class to super-class. For example, in Java one would use the member access modifier private to prevent sub-classes from accessing a particular super-class member (method or field). Nevertheless, it is still possible for the sub-class to override any non-private method in the super-class. This introduces the above mentioned dangerous dependencies between sub-class and super-class. Java has a keyword to control the overriding of class members (final). Conscious use of this feature can definitely be used to tame inheritance based reuse. Unfortunately, that would also limit the flexibility of the framework user. Overriding is one of the key aspects of inheritance and restricting overriding questions the whole concept of inheritance. It would be much better to switch to a different language idiom to achieve reuse. These alternatives will be examined in the next chapter.

The following list summarizes the drawbacks of inheritance.

These are very general statements and wherever there is a rule there also is an exception. There are languages that support dynamic inheritance (SELF). Other languages support parameterized inheritance (C++ templates) and still others have a dedicated concepts for interfaces and inner-classes (Java). This just shows that language designers have searched for alternatives. Some of the techniques and tools which will be examined shortly are based on these alternatives.

1.3 C++: Unidraw

Unidraw [VL90] is a framework for building graphical editors written in C++. One of its authors later participated in the research on design patterns, some which can already be found in Unidraw. In this section I will analyze how multiple patterns were incorporated into the same design and address selected design decisions made by the authors of Unidraw.

1.3.1 Weaving Design Patterns

A design pattern is a way of reusing pieces of software design as opposed to reusing pieces of the software itself. It is a way for experienced software developers to share their knowledge. Although a pattern is something that appears in recurring instances, the applications of a design pattern do not necessarily look identical. A design pattern is usually discovered by analyzing and extracting the similarities between solutions to recurring problems. Design patterns also aid in documenting and understanding software because they establish common terminology and because they structure the design into recognizable pieces. Design patterns focus on the collaboration between objects, whereas traditional object oriented design often focuses on the features of objects and their classification into a hierarchy of generalizations, i.e. classes.

Design patterns are extracts of designs. For the creation of real-world applications and frameworks the reversed process of pattern discovery has to be used; that is, patterns which existed separately before have to be combined with each other - the patterns have to be woven.

Many patterns consist of collaborating objects. Because objects in most OO languages are instances of classes we can also say that classes define the collaboration. Each class taking part in a collaboration is assigned to a role. If software uses more than one design pattern, it is likely that one class plays more than one role simultaneously. So, the process of pattern weaving involves incorporating more than one role into a class. This can be done in a variety of ways. I will examine the different techniques in the next chapters. For now I will focus on an analysis of how Unidraw combines patterns.

The two most central patterns in Unidraw are Observer and Composite.12 The Composite pattern is used to compose objects into a tree structure representing part-whole hierarchies. It lets clients treat individual objects and compositions uniformly. In the Composite pattern the interface of compound objects (objects made of other objects) is the same as that of atomic objects.

Treating composite and atomic object the same may be advantageous for the traversal of the object tree but it leads to the anomaly that atomic objects have a method to add another object to them. Calling this method will cause an runtime error. The Composite pattern trades type safety for transparency (uniform interfaces of atomic and compound objects). There are alternative implementations which are especially useful in languages which offer run-time type checking, e.g. Java.

Weaving Observer and Composite

In Unidraw the two patterns are combined as follows: the roles of the Observer pattern (subject and observer) manifest in two classes - Component and ComponentView. Each component is a pair of Component and ComponentView instances. In addition to the methods needed for the roles in the Observer pattern, both classes also contain methods for iterating over the children. The default implementation of these methods is empty in both classes. For atomic graphical components (components that are represented by visible objects and do not consist of other objects) there are the GraphicComp and GraphicView classes. Compound graphical components are implemented by the classes GraphicComps and GraphicViews. A UML class diagram of this can be found in figure 1.2.


PIC
Figure 1.2: The Composite and Observer patterns in Unidraw.

In Unidraw the interface to compound and atomic components is not as uniform as the standard Composite pattern suggests. It is split into two pieces, child traversal and child manipulation. The traversal interface is indeed uniform - even atomic components have it. The manipulation interface is introduced by compound specializations of Component and ComponentView, e.g. GraphicComps and GraphicViews. This has the advantage that the manipulation interface can be constrained to a particular type of children.

A disadvantage of this technique is the need for duplication of child manipulation code. Of course, there are remedies for this problem (delegation, forwarding) but it still obscures the design. In section 2.4 I will show how type safety can be achieved while avoiding duplication.

Subject-View Symmetry

One also observes what appears to be a minor anomaly in the Observer pattern. Graphical objects like points, lines, rectangles or text, which one would intuitively characterize as pure views, are modeled in Unidraw as components, thus consisting of a subject (GraphicComp) and a view (GraphicView). This leads to the interesting perception that there seems to be a symmetry between subject and view: each view is also a potential subject of another view, which in turn might be the subject of another view, and so on. In [VL90] the authors of Unidraw differentiate “between (1) the state and operations that characterize objects and (2) the way the objects are represented in a particular context.” Apparently, an object’s representation is the state of another object, and this state can be subject of another representation. Unidraw does not offer this flexibility - an object can either be a subject or a view, but not both.

In some cases it can be useful to be aware of the subject-view symmetry. For example, the UML standard defines models as complete abstractions of systems, and diagrams as graphical projections of (subsets of) models [SA98]. This is very close to the idea of the observer pattern. A hypothetical UML authoring tool would use the observer pattern internally to represent the interdependencies between the logical model, e.g. the classes, their members and their relations, and the graphical projections of the model, e.g. class diagrams. Interestingly, the UML standard does not only employ diagrams and models. It also defines a meta-model13 which is needed to specify the UML standard using a subset of UML itself. It is organized into different diagrams, each describing a the system from a different perspective. The perspectives are (source: [SA98]):

It would make sense to mirror UML’s meta-model concept in the architecture of an hypothetical UML authoring tool, such that the meta-model is the abstraction of the system to be modeled. It describes the system on a very general level. This meta-model would be observed by models -- one for each perspective -- which in turn would be observed by the corresponding diagrams. Consequently, a model is both, subject and view, and it plays both roles simultaneously.

Therefore, any modern GUI application framework should support the subject-view symmetry as an integral part of its design. The easiest way to do that is to put subject and view functionality in the base class of the class hierarchy. For example, the ET++ framework which will be described briefly in section 1.5 does that in its VObject class -- the base of all other visual object classes. But doing so leads to overweight base-classes and blurs the conceptual distinction (separation of concerns) between a subject and a view. Ideally, subject and view functionality should be defined and implemented in separate units (classes or interfaces) but there should also be a mechanism to merge the subject and view functionalities in case an application has classes which play both roles simultaneously.

1.3.2 Layout

So far I have discussed how Unidraw employs consistency of representations and part-whole hierarchies. Both are important concepts in the domain of graphical editors. Another concept is layout.

The term layout describes the physical arrangement of components on the output device, affecting their size, position and Z-order.14 The simplest way to layout a toolkit’s components is to have the application programmer manually define the fixed position and size of components. Using a GUI builder in which the programmer can drag components or specify primitive layout rules (grid, position and size alignment) is definitely more satisfying and productive. Sometimes an application allows the user to change the size of the forms. An adjustment of forms to different screen sizes may also be desirable. Resizing a form requires a run-time revalidation of the layout, which is not possible if the coordinates are fixed. For this purpose, most toolkits provide special layout classes that allow a higher level means of arrangement of components. Some GUI builders also support interactive composition and manipulation of such abstract layouts.

A toolkit’s layout classes often assume that the components have rectangular bounds. Although this might be appropriate for GUI toolkits, such a simplification is not reasonable for graphical editors. Consequently, Unidraw provides a much more sophisticated layout.

In Unidraw there are special graphical components called connectors. The connector’s subject is used as a child of subjects belonging to compound graphical components. There is a view for connectors too (a crossed circle), but most compound graphical components omit it in their own view. A connector can be one of three types: pin (zero degrees of freedom), slot (one degree of freedom), and pad (two degrees of freedom). When combined in pairs, connectors form a connection. When a pin is connected to another pin, both will always have the same coordinates. A pin connected to a slot can move along the slot but must stay within the slot’s bounds. A pin connected to a pad can move in both dimensions inside the pad’s bounds. Figure 1.3 illustrates examples of connectors.


PIC
Figure 1.3: Unidraw: Examples of combining different connectors.

Connectors also have a mobility attribute, which can be fixed or floating. This attribute determines which of the connectors in a connection actually moves to satisfy the connection’s constraints. Furthermore, each connection is associated with a piece of “glue” which determines its elasticity and deformation limits.

The layout model is solved by a rather monolithic CSolver class, which solves a network of connections by recursively reducing the connection network. The reduction take place when the solver finds a primitive combination of connections like parallel and series connections. The solver temporarily replaces the combined connections with a single equivalent connection and carries on with the reduction until all connectors are fixed or when there is just one connection left. On return from each level of recursion, the solver reconstructs the original network by applying the deformation of the equivalent connection to each of the original connections.

Unidraw’s layout model is a very mechanical one, making it predictable and logical for both the user and the programmer. The downside of mechanical models is that they might take a longer time to solve than simpler models. What all these layout models have in common is that they describe a set of constraints and that these constraints are solved in order to compute the actual geometry of the components. The type of constraints which describe a layout of graphical components needs to be simple enough such that they can be solved within fractions of seconds. This is because the user should be able to modify the layout interactively. On the other hand, the more powerful constraints are, the easier it becomes for the programmer to describe the layout. Therefore, the designer of an GUI application framework has to find a good tradeoff between the constraint’s complexity and the responsiveness of their solution algorithm.

Some graphical editors may need a layout model with a simple constraint requiring that the components do not overlap. The user can freely drag components around, and the dragged components could “push” the other components aside, just like coins on a table. The constraints in this layout model form a system of linear inequations. Assuming that all components have rectangular bounds and each component has a top, a left, a bottom and a right edge, there would be one equation for every pair of components. For example, two components c1 and c2, each having four edges t1, l1, b1, r1, t2, l2, b2 and r2, yield the following inequality:

(t1 > t2 \/  b1 < t2) /\  (t1 > b2  \/  b1 < b2) \/  (l1 > l2  \/ r1 < l2) /\  (l1 > r2  \/ r1 < r2)

For n components n2 inequalities of the above kind are needed.

This type of constraint cannot be modelled using Unidraw’s connectors. This is because UniDraw does not support the disjunction of constraints. It is impossible to specify that a component may either appear to the left or to the right of another component. In most cases this is not even necessary and Unidraw’s model is sufficient. Ideally, an application framework for graphical editors should employ a means of exchanging the layout model. Based on the simple assumption that components have rectangular bounds, many different types of constraints are possible -- for example, the mechanical Unidraw-like type or the above inequation type.

1.3.3 Commands

Sometimes it is useful to send arbitrary messages to objects without having to (1) declare reception ability, i.e. to ensure that the message is implemented in the receiver and (2) know the receiver in order to issue the message. It is not possible to implement messages like that in C++ or Java using plain methods because method invocation always presumes a receiver instance.

The need to declare the reception ability can be avoided when using virtual methods: The base class of all receivers defines one virtual method for every type of message to be received. In the base-class this method has an empty body. If a receiver wants to implement a particular message it simply overrides the corresponding method. Thus, every message can be sent to any receiver, regardless whether the receiver implements the message or not. If the receiver does not implement the message, it will be caught by the base-class. The downside of this approach is that the base-class needs to be modified when new types of messages are added to the system.

Unidraw uses a variation of the Command design pattern for this purpose. The command pattern “lifts” messages from ordinary methods to objects and makes them first-class citizens. One advantage of the command pattern is that the receiver of a message does not have to be specified when the message is sent. Instead, a reference to the receiver is stored within the command object and the reference is set when the command is created. Usually, the receiver creates the command object and registers it with the sender. The command pattern decouples sender and receiver because the sender does not need to know the receiver of a message.

Another benefit of this pattern is that only one receiving method needs to be declared in the base-class for the receivers. When new types of messages are added to the system, the receiver base-class does not need to be modified. As a result the base class stays lean and manageable. The downside of this is that the messages type has to be decoded in the receiver. If a receiver implements many different messages, this will lead to large switch statements or long chains of if statements.

Furthermore, commands can be used to parameterize the sender. Parameterization in this context means that the sender can issue a command without having to know the particular type of command it issues.

1.4 C++: InterViews

InterViews [LCI+92] is a sophisticated class framework featuring everything from simple geometrical shapes and text up to interactive components (widgets). In InterViews even primitive geometrical shapes and text characters are modeled as objects and the widgets are made up of those primitives.

This is very different to Java’s AWT and Swing where, mostly due to performance reasons, the components simply invoke methods that draw points, lines, boxes and text. AWT/Swing’s and InterViews’ approach differ in the level of physical (object-structural) granularity. In AWT and Swing the most granular object is the widget (aka. component in Java terminology) whereas InterViews’ most granular object is a glyph. In InterViews all visible objects, e.g. points, lines, characters, character strings and even whole popup menus are glyphs. Furthermore, AWT and Swing are not only of limited granularity, they also do not allow true composition of objects - the most central aspect of black-box software reuse. In AWT there is the special component Container which is a direct subclass of Component. But as its name implies it does nothing but contain other components and perform the related activity like input focus handling and layout. The relationship between object composition and black-box reuse will be subject of section 1.6.2.

The problem with the AWT approach is again the amount of duplicated functionality. Soon after AWT was released, third party vendors offered additional components or even sets of components that would replace the original AWT components. If you look at KL-Groups’ tree and table components [KLG], you will find that they completely draw themselves using the drawing primitives in java.awt.Graphics. There is no sign of reuse. In Swing this situation has improved. JTable, for example, uses a variable component to render the cell values. This cell-renderer is implemented in a similar way to a Flyweight, (see section 1.4.1) in a rather awkward way.15 The problem of limited granularity persists in Swing.

1.4.1 The Flyweight Pattern

The downside of fine-granular component structure is the sheer amount of objects and the resultant quantity of storage. The Flyweight pattern deals with this problem and the authors of InterViews were some of the first to discover it. The Flyweight allows sharing of objects by making the important distinction between intrinsic (context-independent) and extrinsic (context-dependend) state. Conceptually different objects, meaning objects that have different extrinsic states but an identical intrinsic state, share one class instance. To perform an operation, i.e. invoke a method on the shared instance, the client has to pass the extrinsic state as an argument to the operation. For a more detailed description see [GHJV95].

The Flyweight in InterViews is the glyph object. The Glyph class does not incorporate any state at all. Sub-classes of Glyph may decide to share instances by making the above mentioned distinction between extrinsic and intrinsic state. Sharing of glyphs creates a glyph structure that is no more purely hierarchical; instead it forms a directed and acyclic graph.

Although a glyph is defined in InterViews as “visual data” it can also be invisible. Often invisible glyphs are used to decorate visible ones. The term decoration originates from the Decorator pattern. A decorator adds responsibilities to the object it decorates and its interface is usually wider than that of the decorated object. The base class for decorating glyphs in InterViews is Glyph’s sub-class MonoGlyph, which simply adds a method for setting and getting the body, that is, the decorated glyph. A typical mono-glyph is InputHandler which can be used to add event-handling to glyphs.

Every glyph may contain other glyphs, but there is also the special class PolyGlyph, which adds detection of structural changes. Apparently, the interface of composite glyphs is spread over two classes: Glyph and PolyGlyph. The same approach can be found in the Composite pattern, allowing for uniform treatment of atomic and compound glyphs when traversing the glyph structure.

1.4.2 Styles

Components often have properties like color and font that determine their appearance. Most GUI toolkits implement such properties as attributes of their component classes. In this case, every component has methods for setting and getting its property attributes, e.g. foreground or background color. Text components also have methods for setting and getting text-specific attributes, e.g. justification or font name.

However, most applications use uniform colors and fonts for all components on their forms. Not doing so would make the user interface look distracting, to say the least. Therefore, all components usually have the same set of properties. Thus, the property attributes in most components have the same value which wastes space for object storage. Furthermore, code needs to be written to set the properties of every single component in the application unless the default values of the properties are used.

InterViews uses styles to decouple the component’s properties from the components. Glyphs which have the same set of properties all share one style. A style is a set of pairs <name,value>. Both, name and value are strings and as such lack type-information.

As the idea of letting components share sets of properties is very innovative, its implementation in InterViews leaves room for improvements. For example, property values should not be strings in order to enforce some degree of type-safety and to eliminate the conversion from strings to actual values. For example, RGB color values should be stored as instances of a Color class or triples of integers rather than strings like "12,34,56". Also, there is no need to identify properties using strings. Integer keys allow for faster lookup and need less storage.

1.4.3 Factory Methods

Toolkits with fine granular component structure often need to assist clients in object composition and creation. InterViews has several classes which specialize on the production and assembly of components. This concept also manifests in two design patterns: Abstract Factory and Factory Method. In InterViews a factory is called kit. The most important kits are WidgetKit and LayoutKit. A widget-kit creates a widget by assembling glyphs according to a concrete look-and-feel. A push-button, for example, is made of a label, two bevels and a mono-glyph that handles the events and invokes the client action. Actions in InterViews are instances of the Command design pattern.

1.4.4 Layout

InterViews’ layout semantics is largely borrowed from Donald Knuth’s TEX [Knu84]. It assumes that the space required by a glyph in order to be drawn is rectangular. Each of the rectangle’s dimensions have natural, minimum and maximum sizes. The glyph’s actual space is a matter of negotiation during which the glyph first requests a certain amount of space. After that it will be granted the amount of space it may actually use, based on the requests of other glyphs sharing the same drawing canvas.

The LayoutKit provides factory methods to

All factory methods return a glyph. The tiling methods return a poly-glyph whereas others take a glyph as an argument and return a mono-glyph wrapping the argument glyph.

1.5 ET++

The ET++ toolkit [WG95] is an object-oriented framework for GUI applications. It is similar to InterViews in that both

ET++ is, however, different to InterViews in that ET++

Two of ET++’s concepts are worth mentioning in this context. One is the notion of bottleneck interfaces and the other one is ET++’s screen update mechanism.

1.5.1 Bottleneck Interfaces

In many cases the interface of a class in object-oriented software can be divided into two sets of methods. These sets can be described as the bottleneck interface and the convenience interface of a class. The methods in the convenience interface are simply front-ends for the methods in the bottleneck interface which do the real work. Overloaded methods are a typical example for that: one of the methods (often the one having the most verbose argument list) is the bottleneck method whereas the other ones are just convenience overloads.

For example, ET++’s VObject (visual object) class has numerous methods to set the origin and size of components: SetExtent(), SetOrigin(), SetContentRect(), SetWidth(), SetHeight(), Align() and Move(). The actual size and origin are altered in SetExtend() and SetOrigin() only. The other mentioned methods all forward to one of these two methods. Hence, SetExtend() and SetOrigin() belong to VObject’s bottleneck interface. In [WG95] the authors say that it “. . . would be bothersome if all these methods had to be overridden in every subclass.” ET++ makes the distinction between bottleneck and convenience methods explicit.

The lack of explicitly documented bottleneck methods has an even worse effect for the application programmer. Often, the application programmer doesn’t even know which methods belong to the bottleneck interface. It may turn out to be hard to figure which methods need to be overridden in application specific subclasses. Java’s java.awt.Component class serves as a bad example. It has 10 (!) methods which affect the position and size of a component. void move(int x, int y)  
void reshape(int x, int y, int width, int height)  
void resize(Dimension d)  
void resize(int width, int height)  
void setBounds(int x, int y, int width, int height) // !  
void setBounds(Rectangle r)  
void setLocation(int x, int y)  
void setLocation(Point p)  
void setSize(Dimension d)  
void setSize(int width, int height) Luckily, the JDK comes with the sources to Component, which reveals that all these methods except one are non-bottleneck methods provided for reasons of either convenience or compatibility. The only bottleneck method is the first setBounds overload. Without access to the sources, this can only be determined by trial and error.

Often, developers of applications and those of frameworks, libraries or toolkits aren’t even aware of the complexity that is introduced by the bottleneck problem. For example, overriding a non-bottleneck method may actually turn out to work as expected in some circumstances. When the application is later extended it may not work anymore because the real bottleneck method gets called and the not the overloaded method. This introduces bugs which are hard to track.

On the other hand, a framework developer might incidentally turn a bottleneck method into a convenience method and vice versa. This may well break existing application code. ET++ demonstrates how this obstacle of inheritance can be avoided by explicitly indicating which methods belong to the bottleneck interface

1.5.2 Screen Update

The authors of ET++ state in [WG95] that application frameworks and class libraries differ in the way they structure the application’s control flow. The user of a class library is required to “. . . know exactly when to call which methods.” A framework, on the other hand, factors out much of the control flow into the framework classes, simplifying the process of writing applications.

Sometimes, factoring out control flow yields cleaner designs and better algorithms too.17 For example, ET++ factors out all of the control flow for the screen update into its view system. That way, redrawing is completely under the control of the redraw system and the update process can be optimized without interfering code of other component classes.

The ET++ update mechanism is asynchronous. The Draw method of components is never called directly. Instead, a component will only be invalidated when it needs to be redrawn due to some change in its internal state. Later, when ET++ is idle, the view system asks all invalidated components to draw themselves. The advantage of asynchronous screen update is that multiple subsequent state changes will not trigger multiple redundant screen updates. The disadvantage is that this scheme does not support animation directly. Animation requires that state changes become visible immediately, i.e. without any noticeable delay.

1.6 SELF: OOP without Classes

So far, this paper was focused on toolkits and frameworks implemented in traditional object-oriented languages. I use the term traditional, because they are largely covered by Booch’s definition [Boo94]:

Object oriented programming is a method of implementation in which programs are organized as cooperative collections of objects, each of which represents an instance of some class, and whose classes are all members of a hierarchy of classes united via inheritance relationships.

This section is about SELF ([ABC+95] and [US91]), a mature object-oriented language that does not adhere to the above definition, in that it does not have the concept of classes. Such OO languages are usually referred to as being prototype-based because they use prototypes for object creation, whereas traditional languages use class instantiation.

A language that does not have classes can not offer class inheritance either, raising the question of how such a language supports reuse. Since we are interested in alternatives to inheritance as a concept for software reuse, it is worth having a look at SELF.

1.6.1 Language Concepts

Here is a summary of SELF’s important features:

SELF can be characterized using two terms: uniformity and minimalism. It is uniform, in that everything is an object and that every object can be used as a prototype for others. Its method lookup unifies closures, methods and objects. SELF is minimal, in that it is untyped, does not differentiate classes and objects, and uses methods for control-flow. It was shown, that SELF can be used to emulate classes without loss in performance. This might indicate that SELF represents the essence of object-oriented languages.

The parent slots of objects are assignable, allowing for dynamic inheritance with the benefit of greater flexibility. It is possible, for instance, to insert a new traits object into an existing inheritance tree at runtime, changing the behaviour of many objects simultaneously. SELF also supports any number of parent slots, thus allowing for multiple inheritance. The method lookup algorithm is even able to handle cycles in the inheritance graph.

SELF’s ability to pass around lexically scoped code-fragments, i.e. blocks, makes it easy to create callbacks from a framework or toolkit into client code. In Java, for example, something similar can only be achieved by having a non-static inner-class implement a framework defined interface. The Java approach suffers from a much noisier syntax and introduces significant overhead because a Java class is not as light-weight as a SELF-Object. Furthermore, SELF’s blocks have access to the lexically enclosing method’s local slots; it is not possible to access the enclosing method’s local variables from within an inner class.

1.6.2 Composition and Delegation

In section 1.2.2 inheritance was already characterized as an example of white-box software reuse. The opposite is black-box reuse, a term yet to be defined. When a white-box is reused, its inner structure and working principles are subject of exploration and manipulation, making it hard to create new releases of the white-box without breaking existing software that already reuses it. A black-box, on the other hand, only exposes its interface. In [Szy99] object composition and delegation are introduced as approaches to black-box reuse. The book also deals with a the problems that arise when traditional OO-languages are used to implement composition and delegation.

Composition is the technique of combining separate objects (components) into a new object (composite).18 The key idea behind composition is that the resulting capability of the composite is equal or close to the sum of its components’ capabilities. Every component specializes on a particular aspect of the composite’s functionality. The composite uses delegation to dispatch incoming messages to its components. Delegation is similar to forwarding in that both pass the message to one of the components. The lack of a common-self [Szy99], also referred to as object-schizophrenia [CE00], causes problems in combination with simple forwarding of messages. When two classes are composed through inheritance, that is, one class inherits from the other, only one object (an instance of the sub-class) is needed to embody both classes’ functionality. Even functionality included by multiple and transitive inheritance is still represented by one object. However, composition uses multiple objects and every component object has a unique identity.

Consider an object that receives a forwarded message (see figure 1.6). Also assume, that the implementation of that message sends another secondary message which is defined in component and overridden in the composite. One would expect that the overriding method in the composite would receive the message first. Unfortunately, if forwarding is used, the secondary message would be received by the component first. This is because the self of the component is still the implicit receiver. However, if delegation were used, the secondary message would be sent to the composite instead. Thus, delegation has to keep track of two selfs, the composite’s and the component’s self.


PIC
Figure 1.6: Object-schizophrenia or the lack of a common self.

The desired behaviour can be implemented in traditional OO languages by instrumenting every method with the additional argument self, which references the current composite. Instead of sending messages to this (as in this.foo() or simply foo()), every component would use self.foo(). Additionally, the composite has to provide dummy methods that forward method calls to the components.

SELF is a language into which composition and delegation are already built-in, and which consequently does not suffer from object-schizophrenia. The composition of objects is formed through the parent slots. The parents are the component, whereas the child is the composite. Delegation is performed by SELF’s method-lookup, which first searches the current object for a matching slot and continues with the current object’s parents, if it did not find one. Furthermore, SELF also has a means for re-sending a message from an overriding method to the overridden one.

1.6.3 Conclusions

SELF demonstrates that OO-languages do not necessarily have to use classes. In SELF there is no distinction between class and object composition. The elegance of this simplified approach also becomes a disadvantage. SELF is dynamically typed, which means that all type checking is done at runtime. The benefit of statically typed programming languages is that the correctness of program can be partially verified by the compiler in an automatic manner.

The authors of SELF claim that the separation of object description and object instance is counter-intuitive and that SELF eliminates this unnecessary complexity. I think that classes are intuitive because they represent how the human mind works anyway: when we think about objects or communicating with others about objects we do not necessarily mean concrete examples of these objects. We treat them as the kinds of objects. According to the SELF authors, class based OO languages cause “meta-regress”: a class is itself an object which needs to be described by another meta-class which is again an object and so on ad infinitum.19

Interestingly, this meta-regress occurs in many areas of computer science. For example, SELF source code is text which describes the initial state and behaviour of a SELF program when it is loaded into the SELF world. This SELF program is a word in the SELF language which is defined by the SELF grammar. In the SELF manual this grammar is specified using EBNF which is, generally speaking, a language itself. The syntax of EBNF can be specified in EBNF itself. This suggests that meta-regress is not counter-intuitive at all - computer scientists and programmers use it all the time.

Chapter 2
Alternative Techniques

The observations made in the previous chapter can be summarized as follows:

Consequently, this chapter is about alternative design and implementation techniques which can be used to develop truly reusable object-oriented software. It focuses on the following questions:

2.1 Weaving Design Patterns

As I have shown in the first chapter object-oriented frameworks should employ design patterns and thus focus on the collaboration between objects. This is already true for some of the frameworks and libraries introduced in chapter 1. Nevertheless, it would be ideal if the client programmer could extend the framework to include new patterns and collaborations. Consequently a good framework should be designed based on the notion of pattern-weaving. Pattern-weaving describes the process of incorporating multiple pattern roles into one class.

Figure 2.1 shows an example where three patterns (A, B and C) are woven into four classes: Foo, Bar, Other and Any. Each box stands for an instance of a class. Each pattern in this example consists of two roles. A role is described by an interface, which is depicted by a small circle. Class instances collaborate in a pattern, when their corresponding role-interfaces are connected.


PIC

Figure 2.1: Design patterns and roles. Boxes denote objects; role interfaces are denoted by small circles and lines ending in small dots indicate collaborations.

In the first chapter I have given examples for toolkits in which it is virtually impossible to do that. C++ with its more flexible language constructs like parameterized and multiple inheritance can certainly offer solutions if someone is stuck with a toolkit that needs to be extended. Java, on the other hand, is more limited than C++ and may thus be less future-proof. The common line of reasoning about how Java compensates for multiple inheritance is that in Java interfaces can have more than one parent interface. But Java only provides the composition of interfaces and not that of implementations. So what is needed is a solution for pattern-weaving in Java. The benefit of a pattern-weaving based design is not only that it supports future extension and thus increased reusability - pattern weaving makes easier to understand and maintain software systems because the role interfaces group the aspects of functionality logically.

It is important to note the difference between pattern-based and pattern-weaving-based designs. A pattern-based design consists of design-patterns. A pattern-weaving-based design also allows for later incorporation of other patterns into the design.

2.1.1 Using Delegation to Weave Patterns

Before I decided that a separate tool was needed to achieve a pattern-weaving based design for a graphical editor framework in Java, I experimented with Java interfaces, inner-classes, composition and delegation. Some of the solutions I came up with represent a good compromise for small systems, which is why I would like to introduce them here. The techniques presented are intermediate steps towards the ultimate solution which is introduced at the end of this chapter.

The first solution is a combination of delegation and composition. It defines role interfaces and compositions of role interfaces, i.e. merger interfaces. The composition of interfaces is simply done using interface inheritance; that is, a merger interface extends the role interfaces which it merges. The role and merger interfaces are implemented by role and merger implementation classes. The basic idea behind this scheme is that the merger implementations delegate the work to aggregated instances of role implementations. I would like to explain this in detail using an example which weaves two design patterns.

Merging the Role Interfaces

Figure 2.2 shows the role interfaces for the two patterns to be woven in this example: Composite and Observer. In Chapter 1 I demonstrated that both patterns represent the core of most GUI frameworks. The Composite pattern incorporates two roles: Leaf and Node. Notice that Node is extends Leaf, indicating that a node is a leaf. The roles of the Observer pattern are Subject and View.


PIC

Figure 2.2: Weaving the Observer and Composite pattern. Step 1: Weaving the role interfaces.

__________________________________________________________________________
Specifying roles as interfaces instead of classes is one of the keys to reusable and extendible software: local variables, method signatures and member variables should not be declared using names of concrete classes. Java interfaces (or to some extent abstract classes) are a better alternative. When client and framework code adheres this minor convention, role implementations can be exchanged without the need to recompile many other client and framework classes. The only place in which names of concrete classes must be used is object instantiation. This can be remedied using factory methods or factory classes, two design-patterns intended to avoid the use of concrete class names for object instantiation.
__________________________________________________________________________

The empty merger interfaces SubjectLeaf, SubjectNode, ViewLeaf and ViewNode represent valid combinations of the role interfaces.1 Merger interfaces are needed to be able to declare variables2 holding objects which implement multiple roles. A SubjectLeaf, for example, is an object that is a leaf and a subject meaning that it can be a part of a node-object and that it can be viewed. Without the merger interface SubjectLeaf it would be impossible to declare variables holding an implementation of both Subject and Leaf.

Implementing the Merger Interfaces

Merging the role interfaces seems fairly straight-forward. But interfaces are not very useful if there are no classes implementing them. I would like to start with an explanation about how the merger interfaces are implemented by classes called merger implementations. The implementation of the role interfaces will be described shortly. For now it is sufficient to assume that the role interfaces are implemented by classes called role implementations.

Figure 2.3 extends figure 2.2 with classes implementing the role and the merger interfaces. The role implementations are merged using object composition and delegation. The class SubjectLeafImpl, for example, implements the SubjectLeaf interface by delegating Subject related messages to Subject implementation and Leaf related messages Leaf implementation. References to these role implementations are held in two private member variables; that is, the role implementations are aggregated3 in the merger implementation. Since aggregation is an example of object composition, it is legitimate to say that the merger implementations are composed of role implementations.


PIC

Figure 2.3: Weaving the Observer and Composite pattern. Step 2: weaving the role implementations

Generally speaking, a merger implementation instance aggregates one role implementation instance for every role interface merged by its merger interface. A merger implementation delegates incoming messages to these aggregated role implementations. Note, that there can be any number of implementations for role and for merger interfaces. The aggregation and delegation related functionality is identical in all merger implementations of a particular merger interface. Thus, it makes sense to factor this functionality out and encapsulate it in a base class - the merger base. There is one merger base class per merger interface. All merger implementations of a particular merger interface are sub-classes of this merger base. The merger bases in the example presented here are SubjectLeafImpl, SubjectNodeImpl, ViewLeafImpl and ViewNodeImpl.

Furthermore, the merger implementation base SubjectLeafImpl contains the abstract factory methods createLeaf() and createSubject() which have to be overridden by concrete sub-classes in order to instantiate Subject and Leaf implementations of their choice. The merger base constructor invokes these methods when it needs to create role implementations. Since the merger base contains abstract methods it is itself an abstract class. Consequently, there should to be at least one concrete merger implementation extending the abstract merger base and providing bodies for the abstract factory methods. Alternatively, the factory methods could be non-abstract and return default role implementations.

Non-empty Merger Interfaces

In the example presented here, all merger interfaces are empty; that is, they do not specify new methods. In some cases, it might be useful to have the merger interfaces specify methods specific to the combination of the role interfaces. These methods, I refer to them as merger methods, should not be implemented by the merger base but by its concrete subclasses.

2.1.2 Object Schizophrenia

In the above section I outlined how merger interfaces are implemented by merger implementations which are composed of role implementations. But where do these role implementations come from? Naively speaking, they are just instances of classes implementing the role interfaces. Unfortunately, every delegation-based approach has to cope with object schizophrenia aka. the lack of a common self: in a composite of many objects, each object has its own this. A detailed description can be found in section 1.6.2.

The Self Reference

The solution introduced in the previous section deals with object schizophrenia by using Inner... interfaces. Inner-interfaces are extensions of role-interfaces and declare a method called self(). The self() methods is called by merger implementations immediately after the role implementations are created. self() sets the common self reference (Figure 2.4). Role implementations implement this extension interface rather than the mere role interface. Whenever a role implementation needs to refer to “itself” it does not use this but self which is the reference that was passed to the self() method. The following sample code demonstrates that.

class SubjectLeafImpl implements SubjectLeaf {  
 
    InnerSubject subject;  
    InnerLeaf leaf;  
 
    SubjectLeafImpl() {  
        leaf = createLeaf()  
        leaf.self( this );  
        subject = createSubject()  
        subject.self( this );  
    }