Reusability Through Containment

Let's say that when we decide to implement the TextRender object we find that another object exists with CLSID_TextImage that is capable of accepting text through IDataObject::SetData but can do nothing more than render a metafile or bitmap for that text through IDataObject::GetData. This TextImage object cannot render memory copies of the text and has no concept of reading or writing text to a file. But it does such a good job implementing the graphical rendering that we wish to use it to help implement our TextRender object.

In this case the TextRender object, when asked for a metafile or bitmap of its current text in IDataObject::GetData, would delegate the rendering to the TextImage object. TextRender would first call TextImage's IDataObject::SetData to give it the most recent text (if it has changed since the last call) and then call TextImage's IDataObject::GetData asking for the metafile or bitmap format. This delegation is illustrated in Figure 6-3.

Figure 6-3. An outer object that uses inner objects through containment is a client of the inner objects.

To create this configuration, the TextRender object would, during its own creation, instantiate the TextImage object with the following code, storing the TextImage's IDataObject pointer in a TextImage field m_pIDataObjImage:


//TextRender initialization
HRESULT     hr;
hr=CoCreateInstance(CLSID_TextImage, CLSCTX_SERVER, NULL, IID_IDataObject, (void *)&m_pIDataObjImage);
if (FAILED(hr))
   //TextImage not available, either fail or disable graphic rendering
//Success:  can now make use of TextImage object.

This code is included here to show the NULL parameter in the middle of the call to CoCreateInstance. This is the outer unknown and is only applicable to aggregation. Containment does not make use of the outer unknown concept and so this parameter should always be NULL.

Now that the TextRender object has TextImage's IDataObject it can delegate functionality to TextImage as needed. The following pseudo-code illustrates how TextRender's IDataObject::GetData function might be implemented:


HRESULT CTextRender::GetData(FORMATETC *pFE, STGMEDIUM *pSTM)
   {
   switch ([format in FORMATETC])
      {
      case <text>:
         //Make copy of text and return
      case <metafile>:
      case <bitmap>:
         //Insure TextImage has current text
         m_pIDataObjImage->SetData(<copy of our current text>);
         return m_pIDataObjImage->GetData(pFE, pSTM);
      }
   return <error>;
   }

Note that if the TextImage object was modified at some later date to implement additional interfaces (such as IPersistFile) or was updated to also support rendering copies of text in memory just like TextRender, the code above would still function perfectly. This is the key power of COM's reusability mechanisms over traditional language-style implementation inheritance: the reused object can freely revise itself so long as it continues to provide the exact behavior it has provided in the past. Since the TextRender object never bothers to query for any other interface on TextImage, and because it never calls TextImage's GetData for any format other than metafile or bitmap, TextImage can implement any number of new interfaces and support any number of new formats in GetData. All TextImage has to insure is that the behavior of SetData for text and the behavior of GetData for metafiles and bitmaps remains the same.

Of course, this is just a simple example of containment. Real components will generally be much more complex and will generally make use of many inner objects and many more interfaces in this manner. But again, since the outer object only depends on the behavior of the inner object and does not care how it goes about performing its operations, the inner object can be modified without requiring any recompilation or any other changes to the outer object. That is reusability at its finest.