Strategy Pattern
Thursday, June 12 2008 - asp-net
How many times have you read something along the lines of "replace switch statement with strategy pattern"?
I've read it a lot and to be honest I never really "got it". I've read a number of patterns books and looked at numerous examples but they always left me wondering how that gets rid of the switch. After all, I thought, you still have to make a determination on what gets called...
It's all about the Pattern
I decided to end my ignorance. It turns out that the problem was with how I was interpreting the pattern. I kept thinking that you would still have to use a switch/if then else or something to figure out what to call, but that simply isn't true. Using the Strategy pattern you move that decision step back up the call stack [I'll probably get smacked around for that statement, but it fits my mental picture].
How about an Example
In order to have this make sense I created a non trivial example. In it we have a class RequestHandlerOriginal that has a single method ProcessUserRequest that takes in an enum value and a userID. Based upon the enum it will call out to the appropriate method. In the below example I left the calls to the Worker.* methods. There isn't really any good reason for this class to exist. In reality these methods could/should be folded into the strategy classes. This cuts down on the actual amount of code you have to maintain. Here's the original: [this is code I just tossed together. It compiles, but that is my only promise]
5 ///
6 /// The original implementation
7 ///
8 public class RequestHandlerOriginal
9 {
10 public enum ActionType
11 {
12 Login,
13 LogOut,
14 ChangePassword,
15 ResetPassword
16 }
17 #region Constructor
18 public RequestHandlerOriginal(){}
19 #endregion
20 #region ProcessUserRequest
21 ///
22 /// Takes in the ActionType and the userID and then based upon the requested action it will
23 /// call the appropriate method
24 ///
25 ///
26 ///
27 public void ProcessUserRequest(ActionType action, int userID)
28 {
29 switch(action)
30 {
31 case ActionType.Login:
32 Workers.ProcessLoginRequest(userID);
33 break;
34 case ActionType.LogOut:
35 Workers.ProcessLogOutRequest(userID);
36 break;
37 case ActionType.ChangePassword:
38 Workers.ProcessChangePasswordRequest(userID);
39 break;
40 case ActionType.ResetPassword:
41 Workers.ProcessResetPasswordRequest(userID);
42 break;
43 default:
44 throw new ArgumentOutOfRangeException("ActionType", action, "Unable to handle the requested action");
45 }
46 }
47 #endregion
48
49 }
96 public void CallOriginal()
97 {
98 RequestHandlerOriginal orig =3D new RequestHandlerOriginal();
99 orig.ProcessUserRequest(RequestHandlerOriginal.ActionType.ChangePassword, 11111);
100 }
50 #region Strategy Base Class
51 ///
52 /// This is the base class that all strategy participants inherit from
53 ///
54 public abstract class RequestHandlerNew
55 {
56 public abstract void ProcessUserRequest(int userID);
57 }
58 #endregion
59 #region Concrete Strategy classes
60 public class RequestHandlerLogin:RequestHandlerNew
61 {
62 public override void ProcessUserRequest(int userID)
63 {
64 Workers.ProcessLoginRequest(userID);
65 }
66 }
67 public class RequestHandlerLogOut:RequestHandlerNew
68 {
69 public override void ProcessUserRequest(int userID)
70 {
71 Workers.ProcessLogOutRequest(userID);
72 }
73 }
74 public class RequestHandlerChangePassword:RequestHandlerNew
75 {
76 public override void ProcessUserRequest(int userID)
77 {
78 Workers.ProcessChangePasswordRequest(userID);
79 }
80 }
81 public class RequestHandlerResetPassword:RequestHandlerNew
82 {
83 public override void ProcessUserRequest(int userID)
84 {
85 Workers.ProcessResetPasswordRequest(userID);
86 }
87 }
88 #endregion
64 public void CallStrategy()
65 {
66 RequestHandlerNew newHandler =3D new RequestHandlerChangePassword();
67 newHandler.ProcessUserRequest(11111);
68 }
74 public class Workers
75 {
76 protected Workers(){}
77 #region worker methods
78 public static void ProcessLoginRequest(int userID){ /*do something*/ }
79 public static void ProcessLogOutRequest(int userID){ /*do something*/ }
80 public static void ProcessChangePasswordRequest(int userID){ /*do something*/ }
81 public static void ProcessResetPasswordRequest(int userID){ /*do something*/ }
82 #endregion
83 }
Conclusion
At the point of the CallOriginal() method call the developer had to make a decision about what action was being taken. The reason I say that is because the enum ActionType had to be set. Likewise with the Strategy pattern the decision about what is being called is made at the time of the call the difference is that it is explicit instead of implicit. The switch statement is no longer needed because we don't have the additional intermediate method.
