Here is the requirement cheat that we received from our client.
- An Eagle is a Bird.
- A Parrot is a Bird.
- A Crow is a Bird.
- A Sparrow is a Bird.
- All Bird can fly.
- Eagle and Crow fly the same way i.e. they print “FAST FLY” when the method is invoked.
- Parrot and Sparrow fly the same way i.e. they print “SLOW FLY” when the method is invoked.
- All birds eat.
- Eagle and Parrot Eat the same way i.e. they print “EAT GRASS” when the method is invoked.
- Crow and sparrow eat the same way i.e. they print “EAT FRUITS” when the method is invoked.
Our goal is to implement this, with in most maintainable fashion or call it best possible solution, which we can think of.
Sounds simple, isn’t it?
All we need is one interface, call it IBird, with fly and eat method. And then Eagle, Parrot, Crow and Sparrow will implement this interface. Base on our current thinking, our class diagram looks something like this:
public interface IBird
{
string Fly();
string Eat();
}
public class Eagle : IBird
{
public string Fly()
{
return "FAST FLY";
}
public string Eat()
{
return "EAT GRASS";
}
}
public class Parrot : IBird
{
public string Fly()
{
return "SLOW FLY";
}
public string Eat()
{
return "EAT GRASS";
}
}
public class Crow : IBird
{
public string Fly()
{
return "FAST FLY";
}
public string Eat()
{
return "EAT FRUITS";
}
}
public class Sparrow : IBird
{
public string Fly()
{
return "SLOW FLY";
}
public string Eat()
{
return "EAT FRUITS";
}
}
Wait a minute!! Are we sure this is best possible thing we can do here? Is this most maintainable/flexible solution that we can possibly have??
Let’s think over this!!!
Aren’t we repeating our self when we implement Fast Fly method in Eagle as well as Crow? Same thing goes for slow fly, eat grass and eat fruit method too. Clearly, we have more than one instance of our algorithms. So it fails on maintainability test (because now we have to remember that every time we change something or fixes some issue, we have to do it at two places)
Let’s run flexibility test now. Our client has come up with following new requirement
A Plane is not a bird.
Plane fly the same way as Eagle and Crow i.e. it print “FAST FLY” when the method is invoked.
Hmmm….. Since we have same behavior as in bird, how about implementing IBird and thereby get the behavior.
public class Plane : IBird
{
public string Fly()
{
return "FLY FAST";
}
public string Eat()
{
throw new System.InvalidOperationException();
}
}
STOP!!!! Does this make sense? C’mon… after all, plane is not a bird. And what does it eat? May be fuel, but our client didn’t specified any requirement related to that. One option could be to leave it as not implemented though.
Grrrrrrrrr!!! You are now violating ISP. Sounds like we are failing on this test too... Shame on us, couldn’t clear even one test.
We need help. F1 please!
Somebody is knocking our door. Let me go and open the door.
Me: Hey, who is there?
Voice from outside: Hey it’s me, Strategy pattern. I heard you screaming for help. I think I can help you. May I come in?
Me: hmmm… sure, why not? So why do you think you can help me with solving my problem?
Strategy Pattern: Well, you see, you are trying to put everything in inheritance base model and that is why you are falling in trap. Rather you can use compositional approach.
Me: ha ha ha… that’s too much jargon, just like Microsoft help. Now can you come to the point and tell me how to solve my problem, instead?
Strategy Pattern: Sure, lets re-consider our approach to this problem here. From the description, it is clear that we are talking about two different set of behavior, Flying and Eating, right?
Me: Ya, I can see that as well.
Strategy Pattern: ok, so lets define two interface called ICanFly and ICanEat, to represent them.
Me: sounds good to me.
Strategy Pattern: Now since we have Fast flyer and Slow flyer, we can implement them in class called FastFlyer and SlowFlyer respectively.
Me: Go on
Strategy Pattern: And then, we have FruitEater and GrassEater , which implement ICanEat. That makes our class diagram look like this:
public interface ICanFly
{
string Fly();
}
public interface ICanEat
{
string Eat();
}
public class FastFlyer : ICanFly
{
public string Fly()
{
return "FAST FLY";
}
}
public class SlowFlyer : ICanFly
{
public string Fly()
{
return "SLOW FLY";
}
}
public class GrassEater : ICanEat
{
public string Eat()
{
return "EAT GRASS";
}
}
public class FruitEater : ICanEat
{
public string Eat()
{
return "EAT FRUITS";
}
}
Me: I am with you. Carry on
Strategy Pattern: Finally, Eagle is fast flyer and eats grass so why not let it implement ICanFly and ICanEat interfaces. In our implementation of Fly, we will delegate this task to FastFlyer and for Eat we will delegate to GrassEater.
Me: Now you are going to tell, we can do same thing for Parrot, Crow and Sparrow too, right?
Strategy Pattern: you are smart! So that gives us new picture like this:
public class Eagle : ICanFly, ICanEat
{
public string Fly()
{
return new FastFlyer().Fly();
}
public string Eat()
{
return new GrassEater().Eat();
}
}
public class Parrot : ICanFly, ICanEat
{
public string Fly()
{
return new SlowFlyer().Fly();
}
public string Eat()
{
return new GrassEater().Eat();
}
}
public class Crow : ICanFly, ICanEat
{
public string Fly()
{
return new FastFlyer().Fly();
}
public string Eat()
{
return new FruitEater().Eat();
}
}
public class Sparrow : ICanFly, ICanEat
{
public string Fly()
{
return new SlowFlyer().Fly();
}
public string Eat()
{
return new FruitEater().Eat();
}
}
Me: Wow, you see that solves my problem, because I am no more repeating my algorithm here. It’s been implemented at just one place
Strategy Pattern: Of course. Do you see any other benefit too?
Me: Hmmm. Let me think………………….
Me: Yes, you see, now I can have plane which implements only ICanFly interface and I am going to delegate actual implementation to FastFlyer again.
Strategy Pattern: right, that gives you
public class Plane : ICanFly
{
public string Fly()
{
return new FastFlyer().Fly();
}
}
Me: And it also passes my second test of flexibility because I could change my design to accommodate new enhancement, very easily.
Strategy Pattern: There you go.
Me: Thank you so much strategy pattern. I am glad that you came here and taught me such an important lesson.
Strategy Pattern: I am glad too, that I could be of some use.
Me: Last thing, I would like to ask though, how do I recognize you in future? Do you have any formal identity?
Strategy Pattern: Oh ya, I am defined as
“Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”
Me: sounds great, and once again thanks for this valuable lesson. I guess we will meet again very soon J
Strategy Pattern: bye bye, and Develop smartly J
-------------------------------------------------X-------------------------------------------------
That was end of my first session on the ongoing series of blog about design pattern. I am planning to upload all my code sample on google code repository. So watch out at http://feeds.feedburner.com/MahendraMavani for more update.
Also, along with my colleague, John Teague, I am going to record screen cast on strategy pattern. This screen cast will briefly cover what I have discussed here and then we will jump to real life usage scenario of strategy pattern in action.
Disclaimer: I am not claiming to be design pattern expert. This is just 2 cent from my side to feel the ocean. I welcome comments, suggest, concern, query and healthy argument on this post. Please feel free to drop your opinion in the comment section.