UW 3.0 improves UW 2.0 by pushing the helper methods to the corresponding subclasses and renaming them "attack". In other words, these methods override the inherited attack method, which may now be declared abstract in the Character class:
The implementation is in:
Examine the Tournament.start method, which repeatedly calls:
c1.attack(c2);
where c1 and c2 are variables of type Character. Each time this line is executed, something different happens. An elf attacks, a robot attacks, a knight attacks, etc.
Of course this was true in UW 2.0, which implemented Character.attack as a multi-way conditional that needed to be updated each time a new subclass of Character is introduced.
However, in UW 3.0 Character.attack is abstract. Instead of the programmer deciding which code should be executed (elf attack, robot attack, knight attack, etc.), the object's themselves are deciding. In other words, if variable c1 happens to hold a knight, then Knight.attack will be called. If it holds an elf, then Elf.attack will be called. (Recall that the subsumption rule tells us that a variable of type Character can hold a reference to an instance of a subclass of Character, which is a good thig. Since Character is abstract, there are no instances of Character.)
The Dynamic Dispatch Rule states:
Objects, not programmers or compilers, decide how to execute the message they receive.
Dynamic Dispatch, combined with Subsumption is called Polymorphism.
Notice how easy it is to add new subclasses of characters. There are no smart classes, so no classes need to be updated. The intelligence of the program—all the different ways there are to attack a victim—are distributed rather than centralized. Distributed intelligence makes a program easier to understand and easier to modify.
We still haven't achieved our main goal, which was to be able to vary the algorithms used by a method from one instance of a class to another. It appears we have done this, but it's a trick. c1 holds references to instances of different subclasses at different times, and these different subclasses have different ideas about how to attack a victim. In other words, it is the execution behavior of the expression c1.attack(c2) that changes, not the attack behavior of different instances of the same class.