Octopull/C++: Using multiple frameworks in C++ and Java |
---|
Home | C++ articles | Java articles | Agile articles | Bridge lessons |
In an application that is using more than one framework there are occasions when it is desirable for a single object to perform a role in two (or more) frameworks. We investigate two design alternatives for combining default implementations provided by the frameworks with a user defined class that interacts with both.
Outline implementation for these designs are given C++ and Java. As a result we reach the surprising conclusion that in changing from one implementation language to the other it is correct to change between these design alternatives.
Sidebar: Application Frameworks, Mixins and Adapters
The idea for this article came from a discussion with a colleague working in Java about the difficulties he was having trying to combine his extensions to a user interface framework (in this case Sun's Swing) with the need to integrate these extensions with a framework that provides an undo facility.
Although I've been dabbling with Java for over two years I still tend to "think in C++" and translate my C++ approach into Java. At the design level this is usually an effective way to apply the experience I've gained over the last decade - it would be surprising if a design stopped working just because it is expressed in a different language. (Of course there are some pitfalls to avoid since C++ supports paradigms that Java doesn't - most notably generic programming.)
My initial suggestion was based on the way I've tackled similar problems in C++, by using the "mixin" idiom to interface to both frameworks and is shown below. Subsequently, I became dissatisfied with using this approach in Java. Upon examining Java code I'd written, I realised that in analogous situations I was using a different approach based on the "adapter pattern". This seems more natural in Java and is also illustrated below.
To make the following discussion concrete, let us assume that we are using two frameworks; one framework delivers the user interface, whilst the second is an engine for a board game such as chess. In writing a program we reach the conclusion that we both want to add a piece to a display in the user interface and to place it in the board. To achieve this we require a class that that implements both user interface behaviour (displaying itself) and application domain behaviour (moving on the board).
Before presenting the code I'd like to make two points:
Before looking at the different approaches to implementing our class we need to provide definitions in C++ and Java of the framework classes with which we will be working:
DisplayInterface
- This defines the protocol used by the display framework to present an object on the screen.
Display
- The part of the user interface that we wish to use to display our piece.
DefaultDisplayInterface
- Provides a default, do nothing, implementation of the interface.
PieceInterface
- This defines the protocol used by the game framework to manage pieces.
Board
- The part of the game framework to which we wish to supply our piece.
DefaultPieceInterface
- Provides a default, do nothing, implementation of the interface.
The first listings 1 and 2 provide corresponding C++ and Java code, so far the differences between the languages are minor and relate only to their syntax.
Listing 1: Framework classes in C++ |
---|
|
Listing 2: Framework classes in Java |
---|
|
In C++ default implementations from several frameworks can be used directly: As can be seen from listing 3, if the default behaviour is all that is required then there is almost nothing to it! (However, this example will be revisited below under "dealing with clashes".)
Listing 3: First approach in C++ |
---|
|
In Java whilst we are able to provide the default implementations, the rules governing inheritance require that we inherit from at most one of them. (Java distinguishes "interfaces" which have no implementation whatsoever from other classes - one may inherit as many interfaces as one likes and implement the required methods, but at most one "ordinary" class.)
As usual an extra layer of indirection solves the problem - listing 4 introduces an extra class whose sole purpose is to delegate the default behaviour for both interfaces whilst allowing derived classes to override those that are of interest.
Listing 4: First approach in Java |
---|
|
Although this code proves that default interfaces can be mixed in Java I'm far from convinced that this is a good approach! There is a serious potential "gotcha" - when there are several methods defined in the interface it is possible for one of the default method implementations to be defined in terms of another - the approach shown doesn't work when only the latter method is overridden.
What I came to realised was that Java's "anonymous local classes" provide a very elegant alternative based on the adapter pattern described by the "Gang of Four"[1] (and that I was already using it on a regular basis).
The use of adapters frees the DisplayPiece
class
from the need to implement the interfaces required by the
framework directly. Also an adapter for one framework doesn't
need to implement any of the functionality required by the other,
so multiple inheritance isn't an issue.
Listing 5 shows an implementation using this approach - the
DisplayPiece
class implements functionality
corresponding to the methods we wish to adapt and two fragments
of "client code" that create suitable adapters for the two
frameworks.
Listing 5: Second approach in Java |
---|
|
The reason for presenting the Java code first in this approach is that in C++ we still have a major design decision to make - something needs to own the adapter classes and to control their lifetimes - they have to be deleted when they are no longer used. (In Java the "virtual machine" reclaims memory that is no longer in use, C++ requires the programmer to know when this happens and to release it herself.)
For the sake of simplicity in listing 6 I've chosen to embed
the adapters as nested classes within the
DisplayPiece
class. This ensures that they will be
deleted with it. Another valid solution is to embed them into a
separate controlling class.
Listing 6: Second approach in C++ |
---|
|
In the first approach we found the Java code to be more elaborate than the C++, now that situation has been reversed. By making the C++ framework more complex and providing a template adapter class we could perhaps reduce the complexity of the client code a little, but the requirement for explicit memory management remains.
Returning to the first approach, there is a possibility that
both interfaces may have a method with the same signature. For
the sake of argument suppose that both interfaces declare a
method void clash()
. If we were to override this
method in our DisplayPiece
class then we would have
difficulty determining through which interface we were being
invoked.
In C++ this issue can be resolved by adding a pair of intermediate classes into the hierarchy that effectively rename the message so that we can determine its origin. Listing 7 shows the way.
Listing 7: Dealing with clashes in C++ |
---|
|
As this technique relies on multiple inheritance there is no corresponding solution in the Java case.
In a very real sense we have been looking at two different formulations of a single idea - an object that can interact with both the display and the board. What is surprising is that the choice of implementation language has such a dramatic effect on the ease with which each approach can be expressed. It could be argued that the correct translation of listing 3 is not listing 4 (which is not idiomatic Java), but listing 5 (which is).
The idea that an "adapter" is the correct translation of a "mixin" seems strange at first but Kevlin Henney[2] has previously observed similar relationship between "iteration" and "enumeration" when moving between languages. This change of form as a result of translation isn't just a characteristic of computer languages - Douglas Hofstadter[3] dedicates a substantial volume to similar interactions between the ideas being expressed and the forms of expression in different natural languages.
Why does changing the implementation language change the way in which the problem is solved?
Mixins and adapters are both idioms I've used before, but I hadn't previously considered the possibility of a relationship between them. I see now that they are very close and solve related problems. The choice between them may depend on the fine detail of the problem at hand.
For those that wish to draw conclusions about the superiority of one language or the other I've shown that both choices are always available in C++, whilst the mixin approach may sometimes be problematic in Java. On the other hand the adapter approach is sufficient in Java and very flexible.
[1] | ISBN 0-201-63361-2 "Design Patterns", Gamma, Helm, Johnson, Vlissides Addison-Wesley 1997 |
[2] | quot;Idioms - Breaking the Language Barrier" Kevlin Henney European C and C++ Users Conference 1998 |
[3] | ISBN 0 7475 3349 0 "Le Ton beau de Marot" Douglas Hofstadter Bloomsbury 1997 |
Sidebar: Application Frameworks, Mixins and AdaptersAn Application Framework is a library of interacting classes that provides both the design and a skeleton implementation of some generic subsystem (for instance a user interface). By modifying the state of some classes (e.g. adding buttons) and specialising the behaviour of others (providing a meaningful response to a button push) the framework is adapted to the needs of the current program. A mixin class is one that defines a protocol required for an operation (such as printing). Inheriting from a mixin class is a declaration that the protocol is supported (i.e. that the class is printable) rather than the more common "is a" relationship. The use of mixins presupposes multiple inheritance (a document is a file, it is also printable). An adapter class[1] is one used to translate between the interface required by some function (printing again) into that supplied by a class that doesn't implement that interface directly but does do something sufficiently similar (provision of display text). An adapter accepts messages from the function and converts them to corresponding ones understood by the target class. |