15.3 Delegation-based Interface Implementation

The delegation-based interface implementation approach is an alternative to using inheritance when implementing CORBA objects. This approach is used when the overhead of inheritance is too high or cannot be used. For example, due to the invasive nature of inheritance, implementing objects using existing legacy code might be impossible if inheritance for some global class were required. Instead, delegation can be used to solve these types of problems. Delegation is a more natural fit doing object implementations when the Process-Entity design pattern is used. In this pattern, the Process object would delegate operations onto one or more entity objects.

In the delegation-based approach, the implementation does not inherit from a skeleton class. Instead, the implementation can be coded as required for the application, and a wrapper object will delegate upcalls to that implementation. This “wrapper object,” called a tie, is generated by the IDL compiler, along with the same skeleton class used for the inheritance approach. The generated tie class is partially opaque to the programmer, though, like the skeleton, it provides a method corresponding to each OMG IDL operation for the associated interface. The name of the generated tie class is the same as the generated skeleton class with the addition that the string _tie is appended to the end of the class name.

An instance of the tie class is the servant, not the C++ object being delegated to by the tie object, that is passed as the argument to the operations that require a Servant argument. It should also be noted that the tied object has no access to the _this( ) operation, nor should it access data members directly.

A type-safe tie class is implemented using C++ templates. The following code snippet explains a tie class generated from the Derived interface in the previous OMG IDL example.

// C++
template <class T>
class POA_A_tie : public POA_A {
public:
   POA_A_tie(T& t)
       : _ptr(&t), _poa(PortableServer::POA::_nil()), _rel(0) {}
   POA_A_tie(T& t, PortableServer::POA_ptr poa)
       : _ptr(&t), _poa(PortableServer::POA::_duplicate(poa)), _rel(0) {}
   POA_A_tie(T* tp, CORBA::Boolean release = 1)
       : _ptr(tp), _poa(PortableServer::POA::_nil()), _rel(release) {}
   POA_A_tie(T* tp, PortableServer::POA_ptr poa, CORBA::Boolean release = 1)
       : _ptr(tp), _poa(PortableServer::POA::_duplicate(poa)), _rel(release) {}
   ~POA_A_tie()
   { CORBA::release(_poa);
   if (_rel) delete _ptr;
   }
   // tie-specific functions
   T* _tied_object () {return _ptr;}
   void _tied_object(T& obj)
{ if (_rel) delete _ptr;
_ptr = &obj;
_rel = 0;
}
void _tied_object(T* obj, CORBA::Boolean release = 1)
{ if (_rel) delete _ptr;
_ptr = obj;
_rel = release;
}
CORBA::Boolean _is_owner() { return _rel; }
void _is_owner (CORBA::Boolean b) { _rel = b; }
// IDL operations*************************************
CORBA::Short op1 ()
{
   return _ptr->op1 ();
}
void op2 (CORBA::Long val)
{
   _ptr->op2 (val);
}
// ***************************************************
// override ServantBase operations
PortableServer::POA_ptr _default_POA()
{
     if (!CORBA::is_nil(_poa))
     {
         return _poa;
     }
     else {
#ifdef WIN32
           return ServantBase::_default_POA();
#else
           return PortableServer::ServantBase::_default_POA();
#endif
         }
   }
private:
    T* _ptr;
    PortableServer::POA_ptr _poa;
    CORBA::Boolean _rel;
   // copy and assignment not allowed
   POA_A_tie (const POA_A_tie<T> &);
   void operator=(const POA_A_tie<T> &);
};

This class definition is a template generated by the IDL compiler. You typically use it by first getting a pointer to the legacy class and then instantiating the tie class with that pointer. For example:

Old::Legacy * legacy = new Old::Legacy( oid);
POA_A_tie<Old::Legacy> * A_servant_ptr =
             new POA_A_tie<Old::Legacy>( legacy );

As you can see, the tie class contains definitions for the op1 and op2 operations of the interface that assume that the legacy class has operations with the same signatures as those given in the IDL. If this is the case, you can use the tie class file as is, letting it delegate exactly. It is more likely, however, that the legacy class will not have identical signatures or you may have to do more than a single function call. In that case, it is your job to replace the code for op1 and op2 in this generated code. The code for each operation typically makes invocations on the legacy class using the tie class variable _ptr, which contains the pointer to the legacy class. For example, you might change the following lines:

CORBA::Short op1 () {return _ptr->op1 (); }
void op2 (CORBA::Long val) {_ptr->op2 (val); }

to the following:

CORBA::Short op1 ()
{
    return _ptr->op37 ();
}
void op2 (CORBA::Long val)
{
   CORBA::Long temp;
   temp = val + 15;
   _ptr->lookup(val, temp, 43);
}

An instance of this template class performs the task of delegation. When the template is instantiated with a class type that provides the operation of the Derived interface, then the POA_Derived_tie class will delegate all operations to an instance of that implementation class. A reference or pointer to the actual implementation object is passed to the appropriate tie constructor when an instance of the POA_Derived_tie class is created. When a request is invoked on it, the tie servant will just delegate the request by calling the corresponding method on the implementation class.

The use of templates for tie classes allows the application developer to provide specializations for some or all of the template’s operations for a given instantiation of the template. This allows the application to use legacy classes for tied object types, where the operation signatures of the tied object will differ from that of the tie class.