How should we use design patterns? - We have to load design patterns in our memory (as many as possible). So that we can recognize places during our design where we can apply them. Design pattern is not reusing code rather it is reusing the experience.

 

In strategy pattern our main concern is to handle the aspects that might vary in future modifications or subclasses. So, we need to identify parts that very and encapsulate them, later we can alter or extend the parts without affecting those that don't. Actually this concept is the basis of all most all design patterns. All patterns try to provide a way to let some part of the system to vary independently of all other parts. We also need to consider another design principle - "Design to an interface, not to implementation". Lets take an example -

"We need to build a system for birds. We should be able to add new birds as we explore the world. Different birds will have different attributes and we will learn about them gradually. First bird that we found is 'aBird'. 'aBird' can fly, eat and talk."

 

So, our first target is to identify what aspect of the application might vary. We need to address three behaviors - eat, talk and fly. We need to ask if each of these behaviors will be same for all birds. Answer is all bird will eat but talking and flying behavior will differ. So, we will implement "eat" functionality in the parent "bird" class and implement "talk" and "fly" functionality separately. In extreme case if we do not have any domain knowledge and there is high possibility of each functionality having different implementations we will need to create separate implementation for each of the behavior. So, in current situation we will create two separate interface for talking and flaying behavior. For each different implementation of these behavior we will create separate implementation classes which will implement the interfaces. In bird parent class we will delegate those to behaviors. In bird subclasses for each bird we will assign respective implementation of the behavior. For example, we will create interface 'talk' and create two implementations - one flying and one non flying. In bird parent class we will delegate the 'talk' interface. Then in bird subclass we will initialize either flying or non flying behavior depending upon requirement. Later if we find a bird which can fly but only very short distance, we can create a new implementation of the flying interface and use that for that bird subclass.

Here is a sample code -

 

Interface iTalkingBehavior {

Public void talk();

}

 

Public class TalkingBird implements iTalkingBehavior {

Public void talk() {

Print("I can talk");

}

}

 

Public class NonTalkingBird implements iTalkingBehavior {

Public void talk() {

Print("I can't talk");

}

}

 

Interface iFlyingBehavior {

Public void fly();

}

 

Public class FlyingBird implements iFlyingBehavior{

Public void fly() {

Print("I can fly");

}

}

 

Public class NonFlyingBird implements iFlyingBehavior{

Public void fly() {

Print("I can't fly");

}

}

 

Public abstract class Bird{

iTalkingBehavior talkingbehavior;

iFlyingBehavior flyingbehavior;

 

Public void performTalk() {

talkingbehavior.talk();

}

 

Public void performFly() {

flyingbehavior.fly();

}

 

Public void performEat() {

Print("I can eat");

}

}

 

Public class aBird extends Bird{

 

Public void aBird() {

flyingbehavior= FlyingBird();

talkingBehavior = TalkingBird();

}

}

 

 

Public class bBird extends Bird{

 

Public void bBird() {

flyingbehavior= NonFlyingBird();

talkingbehavior = NonTalkingBird();

}

}

 

 

Static void main(void) {

Bird myBird1 = new aBird ();

Bird myBird2 = new bBird ();

 

myBird1 .fly(); // output will be "I can fly"

myBird2.fly(); // output will be "I can't fly"

 

myBird1.performEat(); // output will be "I can eat"

myBird2.performEat(); // output will be "I can eat"

}

 

Here we used following design principles -

# Identify the aspects of your application that vary and separate them from what stays same.

# Program to an interface, not to implementation

# Favor composition over inheritance (we used composition for talk and fly functionality)

 

So, strategy pattern defines a family of algorithms, encapsulates each one, makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

 

Note - if we find a new behavior/functionality in a bird then we do not have any solution with current process. For that we will need to use another design pattern with it - Adapter pattern. I will try to explain Adapter pattern in respect to this example in another post.

Comments

ron's picture

Thanks Imon vai for the details discussion with examples.

The example you have shown also demonstrates the use of dependency inversion principle (one of 5 OOD principles from SOLID) and also implements Adapter pattern

Here you wrapped the talink and flying behaviour of Bird with ITalkingBehaviour and IFlyingBehaviour wrappers which is an implementation of Adapter pattern also it shows the composition over inheritence which is an exmple of DI principle.

One thing I want to add. The fly() and talk() methods of NonFlyingBird and NonTalkingBird should probably throw an Exception instead of printing 'I can't fly/talk' and then the controller class should use try..catch block to handle that. Because NonFlyingBird or NonTalkingBird objects should never call fly()/talk() method. The code example is

 Public class NonFlyingBird implements iFlyingBehavior{

Public void fly() {

throw new BusinessLogicException(get_class_name($this).' can not fly!', $code);

}

}

 

 

Public class NonTalkingBird implements iTalkingBehavior {

Public void talk() {

throw new BusinessLogicException(get_class_name($this).' can not fly!', $code);

}

}

 

 

Static void main(void) {

try{

    //.... controller codes here

} catch(Exception $e) {

    // .... handle exceptions here

}

}

 

This extended implementation probably fullfills your example.

 

Just for note, the BusinessLogicException class inherits Exception and overwrites constructor to send (email to developer) or show a detailed debug message with backtrace.

 

 

 

imon's picture

 

Ron bhai, thank you for adding the exceptions :). It certainly added more practical shade on the example. My main target was to display the relation and implementation. 

I want to clarify two things - 

1) In real life scenario we should use bird interface and implement that with the bird parent class.

2) You mentioned in your comment that it is using adapter pattern too. But actually it isn't. None of the interfaces/classes were used to wrap another interface/class to change into a compatible interface/class. We simply identified the items that might vary for the subclasses and took them out from the parent classes. Later we used composition to use them in the subclasses.

"Adapter patter is a design pattern that translates one interface for a class into a compatible interface"

Class diagram of the sample scenario of the strategy pattern is - 

Add new comment