So, as decided in the introduction post, let us start with the ritual of solving a problem without knowing anything about the pattern.
Problem Statement:
There is an Airtel tower in Model Town (with a limited coverage) that can support upto 50 users, maximum. Scheduling of users is a big research problem in telecom industry. 50 users need to be scheduled in certain order. For optimization and simplicity, suppose Airtel identifies that at any given time there can be two types/groups of users (based on the data service they are using) –
Group 1- users engaged in watsapp chatting
Group 2- users playing real time games.
Each group has it’s own set of attributes. Now, user groups need to be scheduled in some order from each group. Airtel want you to design the software of their scheduler such that group 1 users are always scheduled in a Round Robin manner and group 2 users are scheduled using a delay sensitive scheduling algorithm (which is their propriety).
/* Note that I am using ‘Airtel’ only for illustrative purpose here, Airtel may/may not work on these principles. You can read ‘Airtel’ as any ‘ABC’ company for example. */
Getting Deeper:
First Insight to solving the problem would be to have a base class UserScheduler that contains a function schedule. Group1 and group2 classes will inherit the UserScheduler class and override the function schedule to implement round robin and delay-sensitive scheduling respectively. Wait, since UserScheduler class does not need to implement it’s own functions, it does not need to be instantiated and hence, it can be made abstract.
The design looks like this-
Enhancement Required:
Since the mobile applications increased, the users can be grouped into 9 categories based on the data service requirement. Now, they want you to design the software of their scheduler such that –
Group 1 users are scheduled in RR manner
Group 2- Delay sensitive manner
Group 3- RR manner
Group 4- weighted RR manner
Group 5- Proportional fair manner
Group 6- RR manner
Group 7- weighted RR manner
Group 8 – Proportional Fair manner
Group 9 – Delay sensitive manner
Getting Deeper
Thought Process:
We can make 9 classes that implement the abstract base class. Each class would implement it’s own scheduling algorithm. Simple!
Wait, we can see same scheduling manner being used in multiple groups (or classes), this will make the code very redundant. In future if more groups arise, each using one of the 4 scheduling manners or even a newer one, redundancy will increase manifold.
Let us call these manners as algorithms. Currently, there are four scheduling algorithms- RR, weighted RR, Delay sensitive, Proportional Fair. There are 9 group of users, each can implement any one of the four algorithms. These numbers can be changed in future.
Idea:
Let us encapsulate algorithms separately and let these groups use the algorithms without knowing the details.
So, there will be one base class userScheduler that’ll be inherited by 9 groups of users, and 1 base class scheduler that’ll be inherited by 4 scheduling algorithms. The class userScheduler will use the scheduling algorithms, instead of inheriting them. The design looks like the following UML diagram (However, I’ll show only 2 groups and 2 scheduling algorithms in the diagram, rest can be made on similar lines)-
The code looks like-
abstract class scheduler{
void schedule ();
}
class RoundRobin : public scheduler{
void schedule() {
/*Implement RR scheduling algorithm*/
}
};
class DelaySensitive : public scheduler{
void schedule() {
/*Implement Delay Sensitive Algorithm*/
}
};
abstract class UserScheduler{
void scheduleUsingAlgorithm();
}
class Group_1_User : public UserScheduler{
private:
scheduler schedulerObj;
public:
Group_1_User(){
schedulerObj = new RoundRobin();
}
void scheduleUsingAlgorithm(){
schedulerObj.schedule();
}
};
class Group_2_User : public UserScheduler{
privte:
scheduler schedulerObj;
public:
Group_2_User(){
schedulerObj = new DelaySensitive();
}
void scheduleUsingAlgorithm(){
schedulerObj.schedule();
}
};
class Test{
UserScheduler group1= new Group_1_User();
group1.scheduleUsingAlgorithm();
UserScheduler group2= new Group_2_User();
group2.scheduleUsingAlgorithm();
};
This looks good!
Wait What?
Yet Another Enhancement Required:
Design it in a manner such that, group 1 users can switch from RR algorithm to DS algorithm dynamically.
Going deeper
Ummm… this is not much of an effort. We’ll do 3 things-
1. Create a setScheduler method in ‘UserScheduler’ class, that’ll set the scheduler object as RR or DS (which will be given as argument at run time).
2. Move the implementation of function ‘scheduleUsingAlgorithm’ into the class ‘UserScheduler’
3. Make ‘UserScheduler’ as a concrete superclass instead of an abstract class.
The code looks like-
abstract class scheduler{
void schedule ();
}
class RoundRobin : public scheduler{
void schedule() {
/*Implement RR scheduling algorithm*/
}
};
class DelaySensitive : public scheduler{
void schedule() {
/*Implement Delay Sensitive Algorithm*/
}
};
class UserScheduler{
private:
char *listOfSelectedUsers;
scheduler schedulerObj;
public:
void setScheduler(scheduler *ob){
schedulerObj = ob;
}
void scheduleUsingAlgorithm(){
schedulerObj.scheduler();
}
};
class Group_1_User : public UserScheduler{
Group_1_User(){
scheduler rr = new RoundRobin();
}
};
class Group_2_User : public UserScheduler{
Group_1_User(){
scheduler rr = new DelaySensitive();
}
};
class Test{
UserScheduler group1= new Group_1_User();
group1.scheduleUsingAlgorithm(); /*By default, group1 will execute RR scheduling algorithm*/
group1.setScheduler(new DelaySensitive()); /*scheduling algorithm is changed here*/
group1.scheduleUsingAlgorithm();
};
This looks perfect!!
We’ve learned the Strategy Pattern.
Definition: The Strategy Pattern allows to
- define a set of algorithms
- Encapsulate the algorithms, each one separately.
- Select the algorithm at run time.
The class diagram, as given in Wikipedia looks as follows-
Advantages:
- It gives the advantages of Open-Closed Principle in software designing. According to this principle, all software entities (classes, functions, modules etc) should be open for extension and closed for modification. Strategy Pattern provides flexibility by allowing clients to change the code by adding their own algorithms instead of changing the existing ones.
- Allows to use different version of algorithms. A classical tradeoff in implementation is an algorithm that provides time efficiency and the one that provides space efficiency. Using Strategy Pattern, the choice of which algorithm to use can be delegated to the user.
- Allows users to switch between different algorithms at run time.
A Perplexing Consequence:
- The Algorithms usually require data to be passed from the context class. This should be done carefully. Context and the Strategy classes normally communicate through the abstract Strategy base class. Strategy base class must expose all the required behaviours, which some concrete Strategy classes might not implement.