Using an Abstract Class Polymorphically

     

A chief benefit of object-oriented programming is polymorphism. Abstract classes are one way that Java supports polymorphism. That is an issue best left for another topic all to itself. But here, let's look at what is perhaps the most common reason people make abstract classes in the first place: to get flexibility in their design.

Say you have a little Dungeons and Dragons type game where some hero fights monsters. In this adventure, it is possible for the Hero (we'll make him a class) can encounter Serpents or Goblins. Looking at our requirements, we might determine that a Serpent and a Goblin have certain traits in common. They are both monsters. Let's put a capital M on that and make it a class: Monster. Good. Now the Monster class will be the abstract superclass of both Serpent and Goblin, and they will be concrete subclasses. We don't allow any instances of Monster itself ”you have to make some particular kind of Monster.

This is a pretty good design. Because now when we need to make a monster do something in the game, we don't have to clutter up our design worrying about making the Goblin move and fight and then practically doing the whole thing over again making the Serpent move and fight. They will both move and fight, but they will move and fight in different ways. The Goblin will just walk to move. The Snake will slither, which is a more specific kind of movement, different from how it will most frequently be implemented. The Goblin will fight by swinging a sword, and the Snake will fight by biting. Again, they both need to do it, but each does it in its own way.

Why not just make a Goblin class and a Snake class and forget about Monster? Who cares about Monster if the Hero will never actually encounter anything that is a Monster, but only ever subclasses of Monster? Here's why: There are certain things that the Hero does when he or she encounters a monster, regardless of what type of monster it is. These things probably include "fight." Consider what that means in terms of your design.

It might seem okay right now to make a method signature that looks like this: heroInstance. fight(Goblin goblin); . But you have to replicate most if not all of that functionality making another method called heroInstance .fight(Serpent serpent); . Now you're in Copy and Paste land. And that's a dark forest indeed. See where the Monster comes in handy?

Here's a rule : If you find yourself highlighting a method implementation and typing Ctrl+C , you probably need an abstract wedgie in there somewhere. Just don't do it ”don't copy that code. Just release the keys and step away from the laptop. You don't have to live like that. You can be abstract.

Adventure.java

 

 package net.javagarage.demo.abstractclasses.dungeon; /**  * <p>  * @author eben hewitt  */ public class Adventure {     String name;     //constructor that accepts a name for this adventure     public Adventure(String nameIn){         this.name = nameIn;     }     //program starts here     public static void main(String[] arg){         //make a hero to go on adventure         Hero harry = new Hero();         //make a monster:         //dig the polymorphism         //the reference type is the abstract superclass!         Monster gizmo = new Goblin();         //make a new adventure         Adventure chamberOfHorrors              = new Adventure("Chamber of Horrors");         //start them off         chamberOfHorrors.go(harry, gizmo);     }     //do the adventure     private void go(Hero hero, Monster mst){         System.out.println("Welcome to The " + this.name);         //blah blah blah         System.out.println("You hear a                    horrible sound...");         //the important part: call the private method         //that represents the encounter         doCombat(hero, mst);             //all finished         System.out.println("The " +             this.name + " is done.");     }     //we can pass in any subclass of Monster,      //and it will act correctly.     private void doCombat(Hero hero,                           Monster monster) {          String monsterType =                 monster.getClass().getName();         //dig the polymorphism...         System.out.println("Hero sees a " + monsterType + "!");         System.out.println("Will it approach you?");         //dig the polymorphism again         monster.move(12);         //whoa! again!         monster.fight();         hero.fight();         //okay, we get it now.         if (hero.strength > monster.strength)          {         System.out.println("You defeated the                " + monsterType + "!");         } else {         System.out.println("The " + monsterType + "                defeated you!");         }     } } 

Hero.java

 

 package net.javagarage.demo.abstractclasses.dungeon; /**  * <p>Some nerdy do-gooder who fights with Monster  * subclasses in an Adventure  *  * @author eben hewitt  * @see Adventure  * @see Monster  */ public class Hero {     //used to determine who wins fights.     int strength;     //Constructor to make Heroes with.     public Hero (){         //randomly determine strength         //so every hero object is different         this.strength = (int)(Math.random() * 100);     }     //we'll pass a Monster to fight with into     //this method just to show how polymorphism works     //and keep this as simple as we can.     public void fight() {         System.out.println("The Hero attacks         with a sword with         strength " +         this.strength + "!");     } } 

Monster.java

 

 package net.javagarage.demo.abstractclasses.dungeon; /**  * <p>Abstract class, represents an enemy of Hero.  * @author eben hewitt  * @see Hero  */ public abstract class Monster {     //we'll use this as a simple way to     //determine winner of fights     int strength = (int)(Math.random() * 100);     //this will be different for most monster types     String attack;     //most monsters will walk, so we will provide     //a default implementation for them to do that     //so every walking monster in the world doesn't     //need to implement this, since it would be      //roughly the same.call getClass().getName()     //to prove the runtime type     public void move(int numSpaces){         System.out.println(getClass()                    getName() + " walking " +                    numSpaces + " spaces.");     }     //abstract method. we don't know enough about how     //each monster will do this, and it will probably     //be at least a little different for each kind     abstract public void fight(); } 

Goblin.java

 

 package net.javagarage.demo.abstractclasses.dungeon; /**  * <p>A subclass of Monster that is a terrible beast.  * @author eben hewitt  * @see Monster  * @see Serpent  * @see Adventure  */ public class Goblin extends Monster {     //constructor. We don't have a member     //var called 'attack' but remember      //that our superclass Monster does     public Goblin() {         this.attack = "club"; }     //implement fight in some Goblin-specific way     //for this example, that just means hard-coding     //the String 'Goblin' in there     public void fight() {         System.out.println("The Goblin attacks               with a " + attack               + " with strength " + strength + "!");     } } 

Serpent.java

 

 package net.javagarage.demo.abstractclasses.dungeon; /**  * <p>  * @author eben hewitt  */ public class Serpent extends Monster {     public Serpent(){         this.attack = "bite";     }     public void fight() {         System.out.println("The Serpent       attacks with a " + attack               + " with strength " + strength + "!");     }     //remember that Goblins just walkthey can     //just inherit that functionality from Monster.     //Serpents move a different way:     public void move(int spaces){         System.out.println("Slithering " +               spaces + " spaces...");     } } 

Here is the output when we make a new adventure and make the Hero fight a Serpent.

 

 Welcome to The Chamber of Horrors You hear a horrible sound... Hero sees a net.javagarage.demo.abstractclasses. dungeon.Serpent! Will it approach you? Slithering 12 spaces... The Serpent attacks with a bite with strength 28! The Hero attacks with a sword with strength 61! You defeated the net.javagarage.demo.abstractclasses.dungeon.Serpent! The Chamber of Horrors is done. 

The Hero wins! Here is the output when we make a Goblin monster.

 

 Welcome to The Chamber of Horrors You hear a horrible sound... Hero sees a net.javagarage.demo.abstractclasses.dungeon. Goblin! Will it approach you? net.javagarage.demo.abstractclasses.dungeon.Goblin walking 12 spaces. The Goblin attacks with a club with strength 14! The Hero attacks with a sword with strength 5! The net.javagarage.demo.abstractclasses.dungeon.Goblin defeated you! The Chamber of Horrors is done. 

Oops. The Hero lost that time.

The important thing is to notice that we can make any kind of Monster subclass ”Skeleton, Gelatinous Cube, Wraith, Necromancer, whatever we want ”and it won't break any code. We can use Monsters of types we haven't thought of yet, or that someone else thinks of later, and our Adventure class methods can stay the same. Very cool.

Look for places in your application where you can make use of this kind of design. It is extra work up front, but you will see productivity and ease of maintenance benefits later. As my friend from Connecticut always says, "You're going to pay now, or you're going to pay later ”and it's usually cheaper to pay now."



Java Garage
Java Garage
ISBN: 0321246233
EAN: 2147483647
Year: 2006
Pages: 228
Authors: Eben Hewitt

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