Chapter 17. Chain of Responsibility

No one can know everything, so there is a saying that "two brains are better than one." If it's true, then more brains chained together will be even better. Everybody knows a little bit of something and can unite together to form a powerful entity. It's pretty much like families helping each other in the same neighborhood or collaborations among team members on projects in an organization. Every unit in a chain of brains can contribute to tackling problems. If a unit of the chain doesn't know how to handle a problem, then it will pass the problem along the chain and somebody may be able to handle it. Or sometimes, the problem may still be passed along even though there is a unit that knows how to handle it. So a particular process to solve a problem can be complete. It's analogous to an assembly line; every worker knows how to put particular parts to every unfinished product on the conveyer belt. But the product needs to be passed along the chain of workers on the assembly line until the last one to complete it before it can be shipped. Each worker has his own specialties and responsibility on a production line. So the chain of responsibility can be formed like, "I don't know this problem, maybe you would" or "I am done with my part, now it's your turn." This kind of chain of "brains" or "responsibility" allows further upgrades or expansion without modifying what existing units can do. New units are just simply added to the chain, and then the chain will be more capable. Extra workers are added to the end of the line for adding some more parts to the product. The process of adding workers to the production line shouldn't affect other existing workers.

This kind of concept can benefit object-oriented software design when you want to have some objects in the same group handle particular requests and adding or removing handlers in the group should not affect the integrity of the group.

In this chapter, we are going to discuss the concepts of the Chain of Responsibility pattern and how to implement it for the design of a protection mechanism for avatars in a role-playing game (RPG).

What Is the Chain of Responsibility Pattern?

The main idea of the Chain of Responsibility pattern is that an object has a reference to another object of the same type to form a chain. Each object in the chain implements the same method to handle the same request that is initiated on the first object in the chain. If any one of the objects doesn't know how to handle the request, then it will pass the request to the next responder (successor). The relationship between the object that can handle a request and its successor is illustrated in Figure 17-1.

A class diagram of the Chain of Responsibility pattern

Figure 17-1. A class diagram of the Chain of Responsibility pattern

Handler is a high-level abstract class that defines a single method, handleRequest, to handle any request object that any instance of Handler would know how to handle. ConcreteHandler1 and ConcreteHandler2 implement the handleRequest method to handle any request object that they can recognize. Handler also has a reference to another instance of the same type as successor. When a message of handleRequest is invoked on an instance of Handle and the instance doesn't know how to handle the request, it will forward the request to the successor with the same message. If the successor can handle that, then it's fine; otherwise it will pass the request to the next successor if it has any. The process can go on until the request hits the last Handler in the chain. The way that instances of Handler form a chain with their successors is illustrated in an object diagram in Figure 17-2.

A typical object structure of a chain of request handlers at runtime

Figure 17-2. A typical object structure of a chain of request handlers at runtime

aClient has a reference to an instance of Handler as aHandler. aHandler is the first object in the handler chain as aConcreteHandler. aConcreteHandler is connected with another instance of Handler (as aConcreteHandler) with its internal reference as successor. The bottom line of this strategy to handle requests is if you don't know this request, pass it to the next handler (some implementations may still require a handler to pass a request along regardless of whether the handler can handle the request).

Note

CHAIN OF RESPONSIBILITY PATTERN: To avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. It chains the receiving objects and passes the request along the chain until an object handles it.[15]

When Would You Use the Chain of Responsibility Pattern?

You'd naturally think about using the pattern when

  • There is more than one object that may handle a request and the handler is known only at runtime.

  • You want to issue a request to a group of objects without specifying a particular receiver that will handle the request explicitly.

In the next sections, we are going to see how to use the Chain of Responsibility pattern to implement different protection gear for avatars in an RPG.

Using the Chain of Responsibility Pattern in an RPG

Assume that you are building an RPG in which each avatar can earn credits to upgrade its protection gear. Protection gear can be a shield or armor. Each form of protection can handle only a particular type of attack. If the protection gear doesn't recognize a form of attack, then it will pass the effect from the attack to the next "entity" that would respond to that. A visual diagram that illustrates this idea is shown in Figure 17-3.

A visual representation of chained protection layers for an avatar; even though Armor 1 cannot handle an attack, Armor 2 can absorb the attack, and the avatar is intact.

Figure 17-3. A visual representation of chained protection layers for an avatar; even though Armor 1 cannot handle an attack, Armor 2 can absorb the attack, and the avatar is intact.

In Figure 17-3, Armor 1 doesn't know how to handle an attack issued by an opponent, so it passes it on to the next armor, Armor 2. Armor 2 happens to know how to respond to that attack and dissipate the damage that it might have caused to the avatar. For some reason, if none of the armors can respond to an attack, then any effects that come from the attack will eventually be passed to the avatar. When the avatar responds to an attack, it will appear to be a form of damage. This scenario is illustrated in Figure 17-4.

The same visual representation as in Figure 17-3 above, except for this time both armors cannot handle the attack and the avatar absorbs the damage

Figure 17-4. The same visual representation as in Figure 17-3 above, except for this time both armors cannot handle the attack and the avatar absorbs the damage

The mechanism of letting each individual protection gear respond to a particular type of attack and nothing else simplifies the complexity of using different types of protection gear for an avatar. Every type of armor and shield has its own responsibility for a very specific function. This is where the Chain of Responsibility comes into the picture.

We are going to see how to implement this design with the pattern. Let's say we want to have two forms of protection: a metal armor and a crystal shield. They cannot handle any forms of attacks other than the ones that they are designed for. A metal armor can protect an avatar from sword attacks, while a crystal shield can handle any magic fire attacks. As illustrated in previous diagrams, the avatar is also part of the response chain, so it should share the same behavior as other protection gear to respond an attack. Their static relationships are illustrated in a class diagram in Figure 17-5.

A class diagram of a set of AttackHandler that will be formed by a chain of attack handlers

Figure 17-5. A class diagram of a set of AttackHandler that will be formed by a chain of attack handlers

Avatar, MetalArmor, and CrystalShield are subclasses of AttackHandler. AttackHandler defines a single method, handleAttack:attack, which passes an attack to another AttackHandler being referenced as a member variable, nextAttackHandler_, as a default behavior. Subclasses override that method to provide actual responses to an attack. If an AttackHandler doesn't know how to respond to an attack, then it will forward it to super with a [super handleAttack:attack] message so the default implementation in super will pass the attack down the chain.

There are three types of attacks defined in the class diagram: SwordAttack, MagicFireAttack, and LightningAttack. We will discuss which AttackHandler can respond to what type of attack in a little bit. But first, let's take a look at the code for the AttackHandler parent class in Listing 17-1.

Example 17-1. AttackHandler.h

#import "Attack.h"
@interface AttackHandler : NSObject
{
  @private
  AttackHandler *nextAttackHandler_;
}
@property (nonatomic, retain) AttackHandler *nextAttackHandler;
- (void) handleAttack:(Attack *)attack;
@end

AttackHandler defines a private variable of the same type as nextAttackHandler_, which is supposed to be the next responder for an attack. Subclasses of AttackHandler should override the handleAttack: method to respond to a type of attack that it can recognize. The abstract AttackHandler defines a default behavior for that method in Listing 17-2.

Example 17-2. AttackHandler.m

#import "AttackHandler.h"
@implementation AttackHandler
@synthesize nextAttackHandler=nextAttackHandler_;
- (void) handleAttack:(Attack *)attack
{
  [nextAttackHandler_ handleAttack:attack];
}
@end

If a subclass doesn't override that method, then the default implementation of handleAttack: will be called. The method just simply passes an attack to nextAttackHandler_ to handle it.

Let's take a look at the first protection gear for an Avatar, MetalArmor. MetalArmor subclasses AttackHandler and overrides its handleAttack: method, as shown in Listing 17-3.

Example 17-3. MetalArmor.h

#import "AttackHandler.h"
@interface MetalArmor : AttackHandler
{
}
// overridden method
- (void) handleAttack:(Attack *)attack;
@end

As we have seen in other chapters, overridden methods are not necessary to be re-declared in a subclass's header file but are there just for clarity. MetalArmor can recognize only instances of SwordAttack. If an attack is indeed a type of SwordAttack, then handleAttack: will print out a message of @"No damage from a sword attack!" with NSLog. Otherwise, it will print out a different message and forward the attack to super with a [super handleAttack:attack] message, as shown in Listing 17-4.

Example 17-4. MetalArmor.m

#import "MetalArmor.h"
#import "SwordAttack.h"
@implementation MetalArmor
- (void) handleAttack:(Attack *)attack
{
  if ([attack isKindOfClass:[SwordAttack class]])
  {
    // no damage beyond this armor
    NSLog(@"%@", @"No damage from a sword attack!");
  }
  else
  {
    NSLog(@"I don't know this attack: %@", [attack class]);
    [super handleAttack:attack];
  }
}
@end

Likewise, CrystalShield is also a type of AttackHandler. Its class declaration is almost the same as MetalArmor except for the class name, so we are not going to repeat ourselves here. The implementation of CrystalShield is similar to MetalArmor's as well, which recognizes only one type of attack. In this case, it needs to be MagicFireAttack. Otherwise, it will just forward the attack that it doesn't know about to super with the same [super handleAttack:attack] message to let the next AttackHandler take care of it, as shown in Listing 17-5.

Example 17-5. CrystalShield.m

#import "CrystalShield.h"
#import "MagicFireAttack.h"
@implementation CrystalShield
- (void) handleAttack:(Attack *)attack
{
  if ([attack isKindOfClass:[MagicFireAttack class]])
  {
    // no damage beyond this shield
    NSLog(@"%@", @"No damage from a magic fire attack!");
  }
  else
  {
    NSLog(@"I don't know this attack: %@", [attack class]);
    [super handleAttack:attack];
}
}
@end

If none of the protection gear can handle an attack, the attack will eventually get passed to an Avatar. Avatar is also a subclass of AttackHandler and has the same mechanism to respond to any Attack that MetalArmor and CrystalShield do. However, when an attack comes to this point, an Avatar will take it as damage not any protection. Its overridden handleAttack: method prints out the name of an attack being used with NSLog(@"Oh! I'm hit with a %@!", [attack class]), as shown in Listing 17-6.

Example 17-6. Avatar.m

#import "Avatar.h"
@implementation Avatar
- (void) handleAttack:(Attack *)attack
{
  // when an attack reaches this point,
  // I'm hit.
  // actual points taken off depends on
  // the type of attack.
  NSLog(@"Oh! I'm hit with a %@!", [attack class]);
}
@end

Now we have all of our AttackHandlers defined. Let's take a look at client code and see how they can be connected to form a response chain of protection, as in Listing 17-7.

Example 17-7. Client Code That Manages an Avatar and Various Attacks

// create a new avatar
AttackHandler *avatar = [[[Avatar alloc] init] autorelease];

// put it in metal armor
AttackHandler *metalArmoredAvatar = [[[MetalArmor alloc] init] autorelease];
[metalArmoredAvatar setAttackHandler:avatar];

// then add a crytal shield
// to the avatar who's in
// a metal armor
AttackHandler *superAvatar = [[[CrystalShield alloc] init] autorelease];
[superAvatar setAttackHandler:metalArmor];

// ... some other actions

// attack the avatar with
// a sword
Attack *swordAttack = [[[SwordAttack alloc] init] autorelease];
[superAvatar handleAttack:swordAttack];

// then attack the avatar with
// magic fire
Attack *magicFireAttack = [[[MagicFireAttack alloc] init] autorelease];
[superAvatar handleAttack:magicFireAttack];

// now there is a new attack
// with lightning...
Attack *lightningAttack = [[[LightningAttack alloc] init] autorelease];
[superAvatar handleAttack:lightningAttack];

// ... further actions

Our chain of attack handlers is formed like a stack (i.e., first in, last out). We need an Avatar to be the last stop for an attack, so it needs to be created first. Then we create an instance of MetalArmor with the instance of Avatar as its next AttackHandler. They are treated as an "enhanced" Avatar with MetalArmor being the first gate towards the real Avatar instance. Just MetalArmor is not enough; we also need to create an instance of CrystalShield as another form of protection for the Avatar. We use the instance of AttackHandler as a form of MetalArmor (connected with the Avatar) to be the next attack handler for the instance of CrystalShield. At this point, the Avatar is now a "super avatar" with two forms of protection.

At some point in the game, we create three types of attack, swordAttack, magicFireAttack, and lightningAttack for the "super avatar" to handle with its handleAttack: method. We collect some outputs generated from various AttackHandlers in the chain so we can understand what's going on, as shown in Listing 17-8.

Example 17-8. An Output from the Client Code

I don't know this attack: SwordAttack
No damage from a sword attack!
No damage from a magic fire attack!
I don't know this attack: LightningAttack
I don't know this attack: LightningAttack
Oh! I'm hit with a LightningAttack!

The metal armor protected the avatar from the first sword attack and then a magic fire attack, as the second attack didn't do any damage to the avatar either because of the crystal shield. When there was a third attack with lightning, both the armor and shield didn't know how to handle it but spit out a message: "I don't know this attack: LightningAttack". Eventually, the attack was handled by the avatar itself, and it printed out the message "Oh! I'm hit with a LightningAttack!" to indicate the damage caused by the lightning attack.

The simple RPG example illustrates how to use the Chain of Responsibility pattern to simplify the coding and the logic for handling different attacks on avatars. Without using the pattern, the protection logic would most probably be cramped in a single class (e.g., Avatar) and the code for it would be spaghetti-looking.

Summary

We have used the example of implementing different protection mechanism for avatars in an RPG (role-playing game) as the Chain of Responsibility pattern. Each type of protection mechanism can handle only one particular type of attack. A chain of attack handlers can determine what attacks an avatar can be protected from. Any attack handler can be added or removed at any given time during the game without affecting other behaviors of the avatar. The Chain of Responsibility pattern is a natural choice for this kind of design. Otherwise, complex combinations of attack handlers would bloat the avatar and make it difficult to change handlers.

We have discussed a few patterns that focus on extending the object's behaviors with zero or minimal modifications to it. Next, we are going to see how to change the object's behavior by encapsulating and extending its algorithms.



[15] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.240