Aggregating Objects

An aggregate is an object like any other object in that it implements one or more interfaces. Internal to the aggregate, the implementation of certain interfaces is actually provided by one or more contained objects. Users of the object are unaware of this internal structure. They cannot tell and do not care that the object is an aggregate: aggregation is purely an implementation technique.

The figure below shows an aggregate object consisting of a control object that implements Interface A and Interface B, and a noncontrol object that implements Interface C. All these interface implementations are exposed publicly, as indicated by the line and circle extending to the outside of the aggregate.

In the aggregation model, the control object determines how an aggregate behaves and operates, making decisions about which interfaces are exposed outside of the object and which interfaces remain private. The control object has a special instance of the IUnknown interface known as the controlling unknown. The controlling unknown must always be implemented as part of the new code written when the aggregate is put together from other objects. However, when the controlling unknown is passed in, it does not increment the reference count; instead it is a non-incrementing pointer.

The other objects can be implemented at any time. These noncontrol objects can be instantiated separately or as part of the aggregate. However, if these objects are to be capable of aggregation, they must be written to cooperate with the control object by forwarding their QueryInterface, AddRef, and Release calls to the controlling unknown. A reference count for the aggregate as a whole is maintained by the control object so that it is kept alive if there are one or more references to any of the interfaces supported by either the control or noncontrol objects.

It is possible for an object to call a method that may cause the object to be released. A technique known as "artificial reference counting" can be used to guard against this untimely release. The object calls IUnknown::AddRef before the potentially destructive method call and IUnknown::Release after it. If the object in question can be aggregated, it must call the controlling unknown's implementations of AddRef and Release (pUnkOuter->AddRef and pUnkOuter->Release) to artificially increment the reference count rather than its own implementations. For more information about reference counting, see Chapter 2, "The Component Object Model."

The next figure shows how the coordination between the control and noncontrol object works. The control object exposes the controlling unknown, Interface A and Interface B. The noncontrol object supports IUnknown and Interface C. All three interfaces derive from IUnknown.

The control object holds a pointer to the noncontrol object's IUnknown implementation so it can call the noncontrol methods when appropriate. The noncontrol object holds a pointer to the controlling unknown for the same reason. The pointer to the controlling unknown is supplied when the noncontrol object is instantiated. When the controlling unknown pointer is saved by the noncontrol object, AddRef is not called to increment the reference count as you might expect. This is because of the reference-counting cycle it would create. Instead, the noncontrol object just keeps a copy of the pointer and delegates calls to it as described in this section.

Referring again to the next figure, whereas the controlling unknown is available to the outside, as is indicated by the line and circle identifying it extending past the bounds of the aggregate, the noncontrol object's IUnknown implementation works locally and can't be obtained from outside the aggregate. It is called solely by the controlling unknown to obtain instances of the noncontrol interfaces, such as Interface C, to expose to the outside.

Software for developers
Delphi Components
.Net Components
Software for Android Developers
More information resources
Unix Manual Pages
Delphi Examples