In particular, member function name clashes may arise when a particular context object attempts to inherit and implement many different interfaces. In fact, the more interfaces that such an object inherits, the more likely that such a name clash will arise.
Such a clash can be a real show-stopper for large concrete implementation objects, and renaming the operations in one of the conflicting interfaces is not only impractical, but often impossible when using third party libraries.
The Composer pattern makes use of C++ member function pointers to solve this problem in a general way that does not require changing an interface.
Context
, which would use
multiple-inheritance to implement the pure-virtual methods
from two different classes Api1
and Api2
as shown in Figure 1.
However, both of the interfaces Api1
and Api2
have an operation named foo()
with the same signature.
This creates ambiguity and the class Context
cannot be
implemented.
This problem can be solved by using composition, and this can be done generically by applying member function pointers and the C++ template facility.
First, we create the Composers as shown in Figures 2 and 3.
It is interesting to note that the individual Composers
(Composer1
and Composer2
) are associated with the
interface that they implement, and are general enough to be
used in other contexts. This is an improvement over a more
context specific composition solution.
Finally, we show how the individual Composers are used by the a context in Figure 4.
The first important feature to notice is that the Context
(aContext
) implements two member functions with
signatures that match the Api1
and Api2
interface foo()
operations.
The next important feature are the public operations that export
references to the Api1
and Api2
interfaces
to the application context.
The note containing the implementation code in Figure 4, illustrates some details of how a Context makes use of a Composer.
The initializers in the constructor aContext::aContext()
illustrate how the (rather bizzare) C++ syntax for member function
pointers is used to satisfy the constructors for _composer1
and _composer2
. Also note that the first argument to
each is a reference to the Context aContext
using the *this
syntax.
Finally, the operations void aContext::fooApi1()
and
void aContext::fooApi2()
do the actual implementation
work within the aContext
class.
Client
Api
class.
Composer
Composer
is an implementation of the Api
,
which converts
each Api
method/operation into a corresponding member function
pointer call. For general reuse, the Context
should
be associated with the Api
that it implements. This
allows the Composer
to be reused in many Contexts.
The Composer
is implemented as a C++ template, which
may be used in any Context specified by its ctxt
template parameter.
Api
Api
is the interface that is implemented by the
Composer
to allow Contexts to use composition
rather than mulitple inheritance.
Context
Context
is the software that implements the actual
functionality of the Api
through the member function
callbacks that it implements and passes through the Composer
constructor.
Context
implements the abstraction described by the
Api
interface. This abstract interface is defined so as
to enable the Client
to take advantage of different implementations
of the Api
through polymorphism. The Composer
enables the Context
to use composition rather than
inheritance. The composition technique avoids issues
with method name clashes. This is especially important when the
Context
implements many different Api
interfaces
of either the same or different type.
When the Client
invokes the foo()
operation
of the Api
, the foo()
operation of the
Composer
is executed via polymorphism. The implementation
of the foo()
operation within the Composer
invokes the associated member function pointer, which, in this case
invokes the foo1()
operation of the Context
.
It is this foo1()
operation that actually implements the
concrete operation.
Api
.
The Composer pattern allows the user to vary implementations
of the Api
without resorting to modification of an
Api. A Composer can be written once for a
corresponding Api and reused in any Context.
Api
with many operations and
many different function signatures is quite tedious to code.
However, since the Composer is a write-once reuse many
construct, it is usually well worth the effort.
The Composer lends itself to code generation, and it
is conceivable that a Composer template could be
generated directly from its corresponding Api
header file.
#ifndef _apih_
#define _apih_
class Api {
public:
virtual void foo()=0;
};
#endif
#include "api.h"
template
class Composer : public Api {
public:
typedef void (ctxt::* fooOp)();
private:
ctxt& _ctxt;
fooOp _fooOperation;
public:
Composer( ctxt& context,
fooOp fooOperation
);
private: // Api
void foo();
};
template
Composer::Composer( ctxt& context,
fooOp fooOperation
):
_context(context),
_fooOperation(fooOperation)
{
}
template
void Composer::foo() {
(_context.*_fooOperation)();
}
#ifndef _contexth_
#define _contexth_
#include "context.h"
class Context {
private:
Composer _composer;
public:
Context();
private:
void foo1();
};
#endif
#include "context.h"
#include
Context::Context():
_composer(*this,&Context::foo1)
{
}
void Context::foo1() {
printf("foo implementation!\n");
}