Saturday, June 18, 2005

Pr#2 Program to an Interface, not to an Implementation

We are back at the Linxemble Studios, discussing "who should fight using what"! You see two abstractions {Warrior, Weapon}. That's great! Having figured out abstractions is the key towards a successful design (Let's not discuss what's "successful" right here).


Let's look at this design:


As a usual design process, we have declared two interfaces- Warrior and Weapon. The idea is, each Warrior uses it's special Weapon. For instance, a Cavalier uses a Sword and an Archer uses a Bow. Both the Cavalier and the Archer implement the Warrior interface, while the Sword and the Bow implement the Weapon interface. To elaborate, both Cavalier and Archer implement their own set of methods (attack, getWeapon..etc.). Similarly, Both Sword and Bow implement their own "trigger" methods. Say, for the Sword the trigger means "one swipe of the word" and for the Bow it means "shooting the arrow".

Looks easy! We start implementing the specific classes like this:

Let the Cavalier contain an instance of Sword and the Archer an instance of Bow.
For now, let's just look at the Cavalier class. It contains the Sword.



So, things are implemented to some extent (in fact to quite a bit extent!), when another designer comes up with this fantastic idea of training! Here's what he has to say:

"A Cavalier normally fights with a Sword. But he can be trained to be a rifle-horseman! Let's not touch the exesting Cavalier class, as it's instances still will be in use. Let's have another class TrainedCavalier, that extends Cavalier. We need to make it contain Rifle instead of a Sword. This looks quite easy as the Rifle also is an implementer of the class Weapon and as this is an extension, we will have to override only a few methods (like attack, setWeapon) of the Cavalier, reusing the rest of the code! Isn't it?"
So, the obvious solution that comes to mind is extend the Cavalier (as it encapsulates the rest of the features, we just need to change the Weapon!).

But Arghhhhh! The TrainedCavalier class uses an instance of Sword. This is inflexible!

To be precise, the Cavalier instantiates its Weapon as:

Sword swd ;
...
public .... setWeapon()
{
...
swd = new Sword();
...
}
Now the TrainedCavalier can NOT do this, while overriding the setWeapon method:

public .... setWeapon()
{
...
/* This is not possible, as "swd" is of type Sword,
* declared in the base class - Cavalier.
*/
swd = new Rifle();
...
}
You can go ahead and suggest you solutions to Linxemble Studios. But let's think of what could have been a better start. Had the designer of the Cavalier class used this instead:

Weapon wp;

...
public .... setWeapon()
{
...
wp = new Sword();
...
}
the TrainedCavalier could override it as:

public .... setWeapon()
{
...
/*
* This is valid, as "wp" is of type Weapon. Remember IS-A rule?
*/
wp = new Rifle();
...
}

So, the principle under the consideration "Program to an Interface, not to an Implementation"'s violated by the designer of Cavalier class, as he used the specific implementer (i.e, the Sword) of the Weapon abstraction. Now if one wants to refactor the Cavalier class (replacing the references to Sword with Weapon), he has to do so many changes in all the methods which call the methods on the Sword contained by the Cavalier. Current design of the Cavalier class done not make use of the Weapon abstraction well! Why, after all, did we force all the {Bow, Rifle, Sword} implement the interface Weapon?

Well, there is another bad thing going on, which requires us to peek into the actual code to understand it. Even after refactoring the Cavalier class, we'll see some repetition in the code across the Cavalier and Archer classes!

Well, for now, I'd say "You may as well use an Abstract Class!".

---- Discussion to be continued...

Saturday, June 11, 2005

Pr#1 Encapsulate What Varies

Looks like, this comes from the commonsense! After all, that's what the OO-paradigm (OOPS!) is all about! Then why look at it as a Design Principle?

Hmm, well, what OOP tells us is to think in terms of objects (did I not say 'Abstractions'?). So, if you are designing a game similar to the 'Age of Conquerors' in Linux (say, for instance, you are Linxemble Studios!), the first thing you might do is to identify all the objects. To name a few, Warrior{Cavalier, Knight, Archer, RifleMan etc..}, Weapon{Sword, Bow, Rifle}, Building{TownCenter, Barrack, Academy}, Resource{Gold, Stone, Wood, Berry, Sheep, Boar} that will interact. The next would be to see how they will interact. Then, how would you define and update the state of each of the object in the play. So on.

But wait! May I ask, how are you going to implement the algorithm of maintaining the state of any object in the play? You don't know yet! Right? But the design has to proceed, so you need to know exactly how would you maintain the state (think it as a collection of data members, for now).

Ok! Let me be specific. Every Warrior has to have some life! (Yeah, they do ofcourse! I mean, he can fight till he is not dead). Let's have a simple abstraction for the life, say an Integer (** private Integer life **) with a starting value of 10 (MAX_LIFE). Once the value reaches 0, the Warrior dies. Looks simple, huh? You'd ask, who governs the reduction of the life's value? Well, that's what we don't know yet precisely! But we need to device some way to handle it. OR else, can we proceed with the rest of the design (which eventually seems to boil down to similar decisions)? OO-paradigm does not talk about this (well, it's not supposed to!). Using OOP specs, you think, it's the life of a Warrior, so the algorithm to maintain it somehow needs to be encapsulated within the Warrior. And you are stuck because unless you know the algorithm precisely, you can't finish designing the Warrior itself!

Well, the Design Principle "Encapsulate what varies" has the answer! And this is where it differs from the OOP. To understand how, let's try to immitate a novice designer (like us!) who does not know any thing about the Principle. For now he thinks very simple (KISS?!) . He comes up with the following strategy (remember this word, we'll come back to this!).
Well, if the Warrior sits idle, unattacked, his life is no way gonna reduce! But under attack (could be from a wild animal or another Warrior), the reduction of life-value is a function of 'Armour Strength' of the attacked and the 'Attack Power' of the attacker.
So, he atleast for now, proceeds with the design. So it could be a method in Warrior, calculateReduction, that takes the attacker as an argument.

Now comes the real fight, where our Warrior is surrounded by a group of Warriors! The poor designer has to look back into the Warrior-method to accept a Collection (which has its own ramifications). Not a big deal! Some things are meant to change anyways (or we'll lose our jobs!). Later in the evening, the game planner (who does not know any programming) says, dude, I have a fantastic idea - there would be Monk(s) who could heal our Warrior under attack!!

You see the twist in the story, where the Warrior surrounded by attacks, is continually healed by a pack of Monks. And yet another change in the algorithm! You want more? Ok,
let a Warrior A be under attack from a Warrior B who's on a higher ground! Both the Warriors are of same calibre and started with same life, but, I want A to die first (in fact the only Warrior to die, in this situation!) as B is having the advantage of higher land.

And you run wild! This might change not only the warrior but many other!

So, is there any end to this? I don't know! But the principle seems to help us out. It says,
Well, you have discovered that the algorithm to calculate the life-reduction will always vary. Why not encapsulate it!

Well, that's what we try to do! No, not the OOP-way! The principle says, encapsulate the algorithm as an abstraction all together and let the objects under play have access to it. You assume some AbstractLifeCalculaionAlgothithm (which could be implemented later) and go ahead with the rest of the abstractions (Warrior, Nature etc..) . You see, you also have some scope for later optimizations, as now the algorithm is not within the individual objects under play, rather it's a centrally encapsulated entity (LifeCalculaionAlgothithm), which the objects under play have access to. This AbstractLifeCalculaionAlgothithm can change for ever, without forcing the other abstractions to change!

Well, some of you already familiar with Design Patterns, might say "Oh! it's just the Strategy Pattern!". I'd say "I do not care! (BANG!) ". My basis of study is Design Principles, not Designs Patterns (atleast here).

And to be true to you, this Design Principle does not actually differ from the encapsulation in OOP. But to project 'what this principle has so special about it which is not talked in any OOP book', I took the example we discussed.

Now let me talk about Abstractions. All what you saw in grey are examples of abstractions. Abstractions are the heros of our next Design Principle.

Friday, June 03, 2005

The Software Design Principles

Let's first list down these principles that were exposed(and followed) by the pattern-experts and the software-architects world-wide.

  1. Encapsulate what varies.
  2. Program to Interfaces, not to Implementation.
  3. Prefer Composition(or Containment) over Inheritance.
  4. Strive for a loosely coupled design (lower degree of mutual interdependence between the objects)
  5. The Open-Closed principle (The design should be open for extension, but closed for modification-requirement)
  6. The Dependency Inversion principle (Simply put, instead of client having to fret over the disparate Vendor-specific-APIs, force the vendors to follow a common Interface, which the client can simply program to.)
  7. Hollywood-Principle: "Don't call us, we'll call you!" (Let the abstract-class control how to call the methods implemented by the subclasses. Subclasses should have no business deciding the sequence of calls of peer-implemented-methods).
  8. The Single Responsibility Principle (The class should have one and only one reason to change).
  9. Minimum-Awareness-Principle(Law of Demeter): "Only talk to your immediate friends." (Never call a method on an object you got from another object).
  10. The Interface Segregation Principle: (Clients should not be forced to depend on the Interfaces that they don't use)


Now we need to study the contexts in which these priciples work and what happens if we violate them. We'll study various scenarios (problems and patterns used to solve them) and see how these principles are the foci around which the design patterns revolve.

Why Study Patterns?

Well, so many people have already commented and emphasized so much about this that, I feel, there's no need to rephrase it here (which'd be more or less copy-pasting excerpts from the internet)

Let me express few things I feel about patterns in our lives (we can comment on each):
  1. Observing adversary's move-patterns can help you win a game or battle.
  2. We search for typical paterns in satellite-images to do weather-forecasting.
  3. Psychiatrists observe the behavioral patterns of a patient to know his/her mental dissorder.
  4. you can think of more..
So, it looks like we have problems that evolve due to violation of certain basic principles. These priciples seem to be very common (an more common is violating them). This could be the reason we see some repetitiveness in the problems we encounter. You might ask, what principles? Well, that's what we strive to find! And a way is by studying the repetitiveness of the problem - that's precisely the pattern.

So, will that study always help? I don't think so! But, yes, if we are able to correctly decipher the principle that is violated, restoring the principle back increases the probability of getting the problem solved.

So what's next? We need to construct a repertory of 'problems' versus the 'principle(s) violated', which ofcourse requires a lot of experimentation from real life, learning from failures and what not! The good news is, in certain areas, people have already taken pains to do all those. We just need to explore them up!

By the way, does anybody know how a patient is treated in Homoeopathy? This is much similar to applying Design-patterns.

The approach should be to see the problem and understand the basic principle that has been (or is prone to be) violated, rather than opening up a book to find out a pattern that could be applied for a particular problem. This approach could be useful as, the patterns keep evolving, but the principles are more-or-less rigid over the centuries.