Containment - Delegation
Our goal in this module is to study containment/delegation. The general idea behind containment/delegation is that one COM object reuses the services provided by another COM object by "containing" the object to be reused, and making calls into the reused object.
The object doing the containing is called the outer COM object. The object being contained is the inner COM object.
The outer COM object typically mirrors one or more interfaces implemented on the inner COM object. For example, assume the inner COM object implements interface IW
, and IW
has method w1
.
To reuse the services provided by IW
, the outer COM object also implements interface IW
by implementing method w1
. When a call comes into method w1
, in the
outer object's implementation of interface IW
, the outer object calls into the inner, contained, object's implementation of w1
. We say that the outer object "delegates" its call into the inner object.
From the inner object's perspective, the outer object is simply a COM client making calls into it. The inner object is not aware that it is being reused.
Also a client making calls into the outer object is not aware that the outer object is delegating calls into the inner object.
The transparency of the containment/delegation relationship means that no special design or implementation considerations need to be account in the client or the inner COM object.
In fact, only the outer COM object is aware of the containment/delegation reuse of the inner COM object.
Inside Microsoft Programming
Containment/Delegation and Aggregation
COM provides two mechanisms for code reuse called
- containment/delegation and
- aggregation.
In the first and more common mechanism, one object (the "outer" object) simply becomes the client of another, internally using the second object (the "inner" object) as a provider of services that the outer object finds useful in its own implementation. For example, the outer object may implement only stub functions that merely pass through calls to the inner object, only transforming object reference parameters from the inner object to itself in order to maintain full encapsulation. This is really no different than an application calling functions in an operating system to achieve the same ends, other objects simply extend the functionality of the system. Viewed externally, clients of the outer object only ever see the outer object the inner contained object is completely hidden "encapsulated" from view. And since the outer object is itself a client of the inner object, it always uses that inner object through a clearly defined contracts: the inner object's interfaces. By implementing those interfaces, the inner object signs the contract promising that it will not change its behavior unexpectedly.
With aggregation, the second and more rare reuse mechanism, COM objects take advantage of the fact that they can support multiple interfaces. An aggregated object is essentially a composite object in which the outer object exposes an interface from the inner object directly to clients as if it were part of the outer object. Again, clients of the outer object are impervious to this fact, but internally, the outer object need not implement the exposed interface at all. The outer object has determined that the implementation of the inner object's interface is exactly what it wants to provide itself, and can reuse that implementation accordingly. But the outer object is still a client of the inner object and there is still a clear contract between the inner object and any client. Aggregation is really nothing more than a special case of containment/delegation to prevent the outer object from having to implement an interface that does nothing more than delegate every function to the same interface in the inner object.
Aggregation is really a performance convenience more than the primary method of reuse in COM.
Both these reuse mechanisms allow objects to exploit existing implementation while avoiding the problems of traditional implementation inheritance. However, they lack a powerful, if dangerous, capability of traditional implementation inheritance: the ability of a child object to "hook" calls that a parent object might make on itself and override entirely or supplement partially the parent's behavior. This feature of implementation inheritance is definitely useful, but it is also the key area where imprecision of interface and implicit coupling of implementation (as opposed to interface) creeps in to traditional implementation inheritance mechanisms. A future challenge for COM is to define a set of conventions that components can use to provide this "hooking" feature of implementation inheritance while maintaining the strictness of contract between objects and the full encapsulation required by a true system object model, even those in
parent-child relationships.