The Wrong Way Warrior: Where OOP Alone is Not Enough—Part I

In preparing for this year’s OOPSLA conference where my session will focus on demonstrating good practices by showing examples of poor practices, I was reminded of the first chapter of the Freemans’ wonderful book, Head First Design Patterns (O’Reilly). To introduce the reader to design patterns in Java, the Freemans set up an example where a developer got in trouble because his program could not be expanded or changed without taking the whole thing apart and putting it back together again. In their wisdom, the Freemans wanted to kick things off with practical reasons for using design patterns—not just because doing so is the right thing to do.

In some recent discussions, I’ve heard references to OOP and design patterns as a religion or mockingly called “doing it right” as though design patterns are some kind of moral imperative by obsessive-compulsive developers rather than practical solutions to programming problems. Any kind of programming solution should be a solution that does a good job of solving a problem and nothing more. Discussions of OOP and design patterns as the right way to get things done has no resonance with me at all; so I’m the wrong person to suggest that faith in design patterns is sufficient reason to use them.

Perhaps the biggest problem with design patterns is that they’re difficult to learn and other simpler solutions are available. By simpler I’m referring to the fact that the programming and thought processes that go into the solutions are not as difficult. That’s true. However, in the long run, OOP and design patterns make programming much easier because the solutions lend themselves to revision and change. The larger and more complex that projects become, the more important design patterns are for maintaining and updating the program.

The Wrong Way Warrior

The first part of this post will look at a well-meaning OOP programmer who has been given the job of making a prototype for a battle game. To get started the programmer wants to kick things off by creating a Warrior class with several methods that represent what any warrior would have. So he puts the class together with the following methods:

  • setID() – provide a unique ID for each warrior
  • attackRight() – move character to the right
  • attackLeft() – move character to the left
  • retreatRight() – move character to the right
  • retreatLeft() – move character to the left
  • defend() – character does not move but fires weapon
  • takeHit() – character is hit by enemy fire

By subclassing the Warrior class, the developer can build armies with each individual warrior having the properties of the Warrior. That makes perfect sense, and our programmer is creating an OOP-like object. The following listing shows the Warrior class.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package 
{
	import flash.display.Sprite;
 
	public class Warrior extends Sprite
	{
		public var id:String;
 
		public function setID (idNow:String)
		{
			this.id = idNow;
		}
 
		public function attackRight ():void
		{
			this.x += 10;
		}
 
		public function attackLeft ():void
		{
			this.x-=10;
		}
 
		public function defend ():void
		{
			trace (this.id + " holds and fires weapon");
		}
 
		public function retreatLeft ():void
		{
			this.x-=10;
		}
 
		public function retreatRight ():void
		{
			this.x+=10;
		}
 
		public function takeHit ():void
		{
			this.rotation+=5;
		}
	}
}

Right away you might be thinking, “That’s not an abstract class, and it should be.” Well, it does not have a constructor, and giving the programmer his due, we can treat it as an abstract class by not instantiating it. (If that doesn’t work, we’ll blame it on the programmer.)

Subclassing Armies and Building MovieClip Warriors

For images of warriors, we’ll create a couple of movie clips representing a Red Army Warrior and a Blue Army Warrior. Both are placed in the Library and set for Export for ActionScript. One is named RedWarrior and the other, BlueWarrior and each is treated as a separate class. In this way the programmer can create as many instances of each as he wants for the Red and Blue armies. Figure 1 shows each of these creations:

mcwarriors
Figure 1: Movie Clip Warrior Characters

Now that the characters are ready as addressable classes, the next step is to create subclasses from the Warrior class for each as shown in the following listings:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//Red Warrior
package 
{
	import flash.display.Sprite;
 
	public class Red extends Warrior
	{
		//RedWarrior is a movie clip in Library
		private var red1:Sprite=new RedWarrior();
 
		public function Red (x1:uint,y1:uint)
		{
			red1.x = x1,red1.y = y1;
			addChild (red1);
		}
	}
}
 
//Blue Warrior
package
{
	import flash.display.Sprite;
 
	public class Blue extends Warrior
	{
		//RedWarrior is a movie clip in Library
		private var blue1:Sprite=new BlueWarrior();
 
		public function Blue(x1:uint,y1:uint)
		{
			blue1.x=x1,blue1.y=y1;
			addChild(blue1);
		}
	}
}

With concrete classes now for the warriors, including a sprite image representing both the red and blue sides, we’re all set to create a test bench to see if this prototype is ready to develop further. (Note that the programmer even typed the RedWarrior and BlueWarrior instances as Sprite ones—programming to the interface like he should be.)

Testing the Warriors

Now that the basics are completed, we can set up a test bench to see how the program works. All we need is a Client class that implements the warriors and see if the methods fly. This test incorporates buttons to fire the movement methods and trace firing weapons.

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package 
{
	import flash.display.Sprite;
	import fl.controls.Button;
	import flash.events.MouseEvent;
 
	public class Client extends Sprite
	{
		private var redWarrior1:Warrior = new Red(20,30);
		private var blueWarrior1:Warrior = new Blue(400,30);
 
		private var redAttack:Button=new Button();
		private var redDefend:Button=new Button();
		private var redRetreat:Button=new Button();
		private var redHit:Button=new Button();
 
		private var blueAttack:Button=new Button();
		private var blueDefend:Button=new Button();
		private var blueRetreat:Button=new Button();
		private var blueHit:Button=new Button();
 
		public function Client ()
		{
			UIsetup ();
		}
 
		private function UIsetup ():void
		{
			redWarrior1.setID ("Red Hook");
			addChild (redWarrior1);
			blueWarrior1.setID ("Blue Moon");
			addChild (blueWarrior1);
 
			redAttack.addEventListener (MouseEvent.CLICK, redCharge);
			redDefend.addEventListener (MouseEvent.CLICK, redHold);
			redRetreat.addEventListener (MouseEvent.CLICK, redBugout);
			redHit.addEventListener (MouseEvent.CLICK, redWound);
			blueAttack.addEventListener (MouseEvent.CLICK, blueCharge);
			blueDefend.addEventListener (MouseEvent.CLICK, blueHold);
			blueRetreat.addEventListener (MouseEvent.CLICK, blueBugout);
			blueHit.addEventListener (MouseEvent.CLICK, blueWound);
 
			addChild (redAttack);
			redAttack.label = "Attack";
			redAttack.x = 20,redAttack.y = 300;
			addChild (redDefend);
			redDefend.label = "Defend";
			redDefend.x = 20,redDefend.y = 322;
			addChild (redRetreat);
			redRetreat.label = "Retreat";
			redRetreat.x = 20,redRetreat.y = 344;
			addChild (redHit);
			redHit.label = "Outch!";
			redHit.x = 20,redHit.y = 366;
 
			addChild (blueAttack);
			blueAttack.label = "Attack";
			blueAttack.x = 400,blueAttack.y = 300;
			addChild (blueDefend);
			blueDefend.label = "Defend";
			blueDefend.x = 400,blueDefend.y = 322;
			addChild (blueRetreat);
			blueRetreat.label = "Retreat";
			blueRetreat.x = 400,blueRetreat.y = 344;
			addChild (blueHit);
			blueHit.label = "Outch!";
			blueHit.x = 400,blueHit.y = 366;
		}
 
		private function redCharge (e:MouseEvent):void
		{
			redWarrior1.attackRight ();
		}
		private function redHold (e:MouseEvent):void
		{
			redWarrior1.defend ();
		}
		private function redBugout (e:MouseEvent):void
		{
			redWarrior1.retreatLeft ();
		}
		private function redWound (e:MouseEvent):void
		{
			redWarrior1.takeHit ();
		}
 
		private function blueCharge (e:MouseEvent):void
		{
			blueWarrior1.attackLeft ();
		}
		private function blueHold (e:MouseEvent):void
		{
			blueWarrior1.defend ();
		}
		private function blueBugout (e:MouseEvent):void
		{
			blueWarrior1.retreatRight ();
		}
		private function blueWound (e:MouseEvent):void
		{
			blueWarrior1.takeHit ();
		}
	}
}

That looks like a pretty big Client class, but all it does is to create a single instance of the red and blue warriors and lots of buttons to test the methods. The set of buttons on the left operate the Red Warrior and the ones on the right operate the Blue Warrior. Again, the programmer is no straw man—he programs to the interface to instantiate both the Red and Blue warriors. What we are questioning is how sustainable and expandable is this design?

When you test the program you should see the initial image shown in Figure 2:

initial

Figure 2: Initial Positions of the Instances

Go ahead and test it. (You’ll find yourself having seconds of fun!). Everything works, and if you add additional characters, they’ll show up on the screen, but you’ll need separate sets of buttons to work them. However, that’s not the point. In developing the game all of the methods will be triggered either by program-generated events or a more sophisticated UI. So, for the time being just think of the buttons as tools for testing and not an integral part of the program.
play

What’s Wrong with this Picture?

First of all, it works. However, that’s not the point. The concept, “If it ain’t broke, don’t fix it” either fails to recognize it will break down the road or flees from innovation. (It’s sort of like the wanker without a girlfriend—he thinks this is a good as it gets.)

What will happen when this game is expanded? (Keep in mind the buttons are just a temporary testing device.) How do we accommodate changes in the weapons? What if we want to have special capabilities for each side? How can movement be enhanced (with rocket boots or magic capabilities). How can we make this game model more flexible?

My next post follows the Freemans’ lead and provides an alternative way to develop the game by introducing a design pattern. In the meantime, I’d like to hear readers’ ideas and comments on how to improve the development process. What would be a better way to start off creating a project like this? Are design patterns available to make this more sustainable? Expandable? Let’s hear what you think.

11 Responses to “The Wrong Way Warrior: Where OOP Alone is Not Enough—Part I”


  • Good stuff. Looking forward to next post!

  • Smells like the Stategy pattern…

  • Tolis,

    You’ve got a good nose…

    Cheers,
    Bill

  • Strategy for exchanging weapons and behaviour, factory for seperate construction from the client, maybe decorator pattern for adding additional supplements like clothing.. to add phyics or at least collision detection I would add something like a “world” class that listens to events from the warriors and moves them accordingly.

  • Ayee Chihuahua Mark!

    Those are great ideas, and I have to admit that I got sort of carried away myself when I started re-wiring the project. However, rather than creating an entire system as you suggest (and not a bad system at that!) I’m going to provide something simpler that will solve certain problems and focus on a single organizational unit.

    However, Chandima and I always encourage our readers to go ahead and carry out suggested designs.

    Kindest regards,
    Bill

  • I’m looking forward to read your solution. Do you consider it possible that you speak on a german conference someday? http://www.flashforum.de does a conference every year and your stuff would perfectly fit.

  • Looking forward for the next post! Doing mistakes is a great way to learn! :)

    All flash developers should read your blog! Keep up the good work.

  • Hi FLASHize,

    Thanks for the comments. I’m now working on the next post, and it’s turning out to be more work than I bargained for! Anyhow, Chandima and I are delighted to hear that our efforts are helping out developers.

    Kindest regards,
    Bill

  • Hi Mark,

    I hope the solution is worth the wait. It’s a lot of work!! Years ago I spoke at Heidelberg University in Germany and enjoyed the German students’ a great deal and would be glad to speak there again. (I’ll show you my dueling scar from Heidelberg!)

    Mit freundlichen Grüßen,
    Bill

  • can’t wait for the follow-up. Head First Design Patterns is a favorite, I actually had it out yesterday to refresh a few things.

  • Hi T,

    I have it out a lot too! What it does better than any other book is to show in detail WHY a certain design pattern or principle is used. The style of this blog and our own ActionScript 3.0 Design Patterns book owes a great deal to the Freemans’ lead.

    I have the follow-up application completed, and now I’m working on the write-up. The Freemans really set a high bar. (gulp!)

    Kindest regards,
    Bill

Leave a Reply