4.4 Composition and delegation


We say that ClassB is composed with ClassA if ClassB has a ClassA or ClassA* member; for short we can say ClassB has a ClassA. And, as before, ClassB inherits from ClassA if ClassB is derived from ClassA as a child class; for short we say ClassB is a ClassA.

As it turns out, you can always replace an inheritance relationship by a composition relationship as indicated in Figure 4.7. If ClassB has a ClassA member object *_pA, then (a) a ClassB object gets a set of ClassA data fields wrapped up inside *_pA and, (b) ClassB can implement the same methods as ClassA simply by passing these method calls off to *_pA. When you pass method calls to a composed object, this is called delegation .

Figure 4.7. Inheritance and composition

graphics/04fig07.gif

Note that you can also do composition and delegation by using a member object ClassA _mA, but we prefer pointer members because they permit polymorphic function calls.

Why would you want to use composition in place of inheritance? There are several reasons.

First, C++ code using multiple inheritance tends to be a bit difficult to maintain, and there are special MFC CRuntimeClass methods and macros that would need to be overridden if you want to use multiple inheritance. If you have a ClassB that you'd like to have inherit from both ClassA and ClassC, you can instead use composition for ClassA or ClassC. Figure 4.8 shows how this looks if we compose ClassB with ClassC.

Figure 4.8. Use composition to avoid multiple inheritance

graphics/04fig08.gif

Second, inheritance locks in a class's behavior at link time, while composition allows you to change the behavior of a class during runtime. This is illustrated in Figure 4.9. The ClassB has a set_pA member method to delete the old *_pA and install a new one. In the Pop Framework, when you use the Player menu to change the player's controls, you are actually changing the kind of cListener *_plistener member which the player cCritter is composed with.

Figure 4.9. Composition makes dynamic change possible

graphics/04fig09.gif

Third, inheritance is sometimes called 'white box' code reuse, because when you inherit from a class its internals are visible to you. Composition, on the other hand, is called 'black box' code reuse because (unless you've unwisely used a friend statement) the internals of the class you compose with are hidden. A practical advantage of black box code reuse is that you're less likely to break things that are used by classes other than your own. A useful mental model when using composition is that you're making a class by snapping together preexisting components .

A fourth and final reason why we often prefer composition to inheritance is that composition lets us avoid the 'combinatorial explosion' that we end up with if we try to separate out a class for every possible combination of the behaviors that we would otherwise delegate out to a composed member.

Of course there are still many situations where inheritance is the appropriate design method. Particularly if you're interested in having a polymorphic set of objects, it's good to have the objects inherit from a common base class. In the case of the Pop Framework, the cCritter base class plays this role. The individual cCritter child classes have constructors which compose specialized critters by 'snapping together' some component classes. And the individual cCritter update methods are usually overridden. We use inheritance so as to have a uniform list of cCritter child objects, and we use composition both to create new kinds of cCritter child classes and to possibly change the cCritters while the program is running.

Look, for instance, at the diagram of the critters and the classes they compose with (Figure 4.10). We see two kinds of critters, two kinds of sprites , two kinds of listeners, and two kinds of forces, eight classes in all. Now suppose that we wanted to avoid composition and put all of the behavior into the classes. Unless we use multiple inheritance, we'd end up with 18 classes: cCritter and cCritterArmed , with eight child classes each, one child for each of the eight ways of choosing polygon/icon, cursor/fly, or gravity/evade. This is illustrated in Figure 4.11. But if we can use composition to farm out the choices to helper classes, then we end up with a smaller number of classes in all.

Figure 4.10. Critters and classes they are composed with

graphics/04fig10.gif

Figure 4.11. Combinatorial explosion of classes

graphics/04fig11.gif

Now let's say a bit about the practicalities of composition and delegation. When you compose ClassB with a ClassA member _mA (or with a *_pA member) the owner ClassB will need to use ClassA accessors and mutators to get at the ClassA object's data. You can get around this by having ClassA declare ClassB as a friend, but generally we try to avoid friend statements as they break encapsulation.

When you use composition with delegation as illustrated in Figure 4.7, you need to explicitly declare and implement a ClassB function like foo() which is intended to pass off the call to the ClassA member method foo(). This is different from inheritance, where a child class automatically gets the methods of the parent class. In the case of composition, you of course don't have to give the ClassA method the same name as the ClassB method which it calls. In fact it is likely to make your code easier to understand if you give the ClassA method a name like 'feelfoo' or 'dofoo' or 'callfoo.'

A fairly trivial example of composition is that we give both our cSprite and our cRealBox classes a cColorStyle*_pcolorstyle member which holds things like the fill color to be used for the shape. The color- related mutators and accessors for cSprite and cRealBox pass the calls on to their _pcolorstyle members. This is shown in Figure 4.12.

Figure 4.12. Simple composition

graphics/04fig12.gif

Why didn't we just make cSprite and cRealBox inherit from, say, a common cUsesColorStyle class? The reasons were that (a) other than being drawable with colors, the two classes really have nothing in common and, more importantly and (b) as we move from two-dimensional graphics to three-dimensional graphics, we'd like to allow the possibility of using richer and more complicated kinds of cColorStyle child classes to specify the colors and styles of our sprites and world boxes.

A less obvious point about delegation is that when ClassB delegates a method like foo(), you often want foo to be able to access and mutate the members of ClassB. If ClassB has a ClassA *_pA member, the correct way to delegate foo() so that it can access and mutate ClassB is the following.

 ClassB::foo()  {      _pA->foo(this);  }  ClassA::foo(ClassA *powner)  {      /* Use ClassA accessors and mutators to read and change the fields          of powner */  } 

In the specific example of the cCritter and the cListener *_plistener that it's composed with, we have the following code.

 void cCritter::listen(Real dt)  {      _plistener->listen(this); /* We pass the pointer "this" to the          listener so that it can change the fields of this calling          cCritter as required. */  }  void cListenerScooter::listen(cCritter *pcritter)  {      cController *pcontroller = pcritter->pgame()->pcontroller();/*The          caller critter's pgame() holds the cController object that          stores all of the keys and mouse actions you need to possibly          listen to in here.*/  //Translate      if (pcontroller->keyonplain(VK_UP))          pcritter->setVelocity(pcritter->maxspeed()*              pcritter->tangent());              /* I want to move the critter position. But I don't just                  use a moveTo because I want to have a correct _velocity                  inside the critter so I can use it to hit things and                  bounce and so on. So I change the velocity. */      //Etcetera....  } 

In other cases it may be that the foo call does some setup code before passing the call off to the composed object. This is the situation where cCritter delegates some of its draw call to its cSprite *_psprite member. The matrix manipulations serve to translate and rotate the graphics frame of reference to match the critter's position and orientation.

 void cCritter::draw(cGraphics *pgraphics, int drawflags)  {      pgraphics->pushMatrix();      pgraphics->multMatrix(attitude());      _psprite->draw(pgraphics, drawflags);      pgraphics->popMatrix();  } 


Software Engineering and Computer Games
Software Engineering and Computer Games
ISBN: B00406LVDU
EAN: N/A
Year: 2002
Pages: 272

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net