Is this how dynamic language copes with dynamic requirement?
- by Amumu
The question is in the title. I want to have my thinking verified by experienced people. You can add more or disregard my opinion, but give me a reason.
Here is an example requirement: Suppose you are required to implement a fighting game. Initially, the game only includes fighters, who can attack each other. Each fighter can punch, kick or block incoming attacks. Fighters can have various fighting styles: Karate, Judo, Kung Fu... That's it for the simple universe of the game. In an OO like Java, it can be implemented similar to this way:
abstract class Fighter {
int hp, attack;
void punch(Fighter otherFighter);
void kick(Fighter otherFighter);
void block(Figther otherFighter);
};
class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };
This is fine if the game stays like this forever. But, somehow the game designers decide to change the theme of the game: instead of a simple fighting game, the game evolves to become a RPG, in which characters can not only fight but perform other activities, i.e. the character can be a priest, an accountant, a scientist etc... At this point, to make it more generic, we have to change the structure of our original design: Fighter is not used to refer to a person anymore; it refers to a profession. The specialized classes of Fighter (KaraterFighter, JudoFighter, KungFuFighter) . Now we have to create a generic class named Person. However, to adapt this change, I have to change the method signatures of the original operations:
class Person {
int hp, attack;
List<Profession> skillSet;
};
abstract class Profession {};
class Fighter extends Profession {
void punch(Person otherFighter);
void kick(Person otherFighter);
void block(Person otherFighter);
};
class KarateFighter extends Fighter { //...implementation...};
class JudoFighter extends Fighter { //...implementation... };
class KungFuFighter extends Fighter { //...implementation ... };
class Accountant extends Profession {
void calculateTax(Person p) { //...implementation...};
void calculateTax(Company c) { //...implementation...};
};
//... more professions...
Here are the problems:
To adapt to the method changes, I have to fix the places where the changed methods are called (refactoring).
Every time a new requirement is introduced, the current structural design has to be broken to adapt the changes. This leads to the first problem.
Rigid structure makes it hard for code reuse. A function can only accept the predefined types, but it cannot accept future unknown types. A written function is bound to its current universe and has no way to accommodate to the new types, without modifications or rewrite from scratch. I see Java has a lot of deprecated methods.
OO is an extreme case because it has inheritance to add up the complexity, but in general for statically typed language, types are very strict.
In contrast, a dynamic language can handle the above case as follow:
;;fighter1 punch fighter2
(defun perform-punch (fighter1 fighter2) ...implementation... )
;;fighter1 kick fighter2
(defun perform-kick (fighter1 fighter2) ...implementation... )
;;fighter1 blocks attacks from fighter2
(defun perform-block (fighter1 fighter2) ...implementation... )
fighter1 and fighter2 can be anything as long as it has the required data for calculation; or methods (duck typing). You don't have to change from the type Fighter to Person. In the case of Lisp, because Lisp only has a single data structure: list, it's even easier to adapt to changes. However, other dynamic languages can have similar behaviors as well.
I work primarily with static languages (mainly C and Java, but working with Java was a long time ago). I started learning Lisp and some other dynamic languages this year. I can see how it helps improving my productivity.