Improving Efficiency With Strategy Design Pattern In JSON Parser Example

Here we will see how Strategy Pattern helped in refactoring code in JSON parser example

 

Strategy Pattern falls under the behavioral patterms category in Gang of Four Design Patterns. Let’s take a real world example. In your office building your workstation is on the first floor while your colleague has one on the seventh floor. Both of you are in a hurry to reach your  workstation (i.e same task), but if both of you take the lift or take  the stairs (i.e follows same approach) it will not be the best strategy, respectively. If you take the stairs to reach the first floor and your colleague takes the lift to reach the seventh floor (i.e different strategies) then this will be the best approach by each of you.

In the above scenario you can observe that the problem is the same but based on other parameters the same approach will not prove best in every case. In our day to day code we face similar kinds of problems, here Strategy Design Pattern comes to the rescue, the name itself suggests that it’s about making strategies.

Code Example - JSON Parser

JSON is the most popular data exchange format today. Suppose there is one system which needs to parse JSON data coming from various sources. This system was working fine, until the dev team realized that although all data is in JSON format there’s a variety of data like, weather sensor data which has mostly numeric values, blog articles which have large text blocks, chat conversations which have smaller text blocks with emojis. Dev team sees an opportunity to optimize the performance of the system using different parsers according to the source of data. Let’s see how they addressed the problem and refactored towards maintainable code.

Below is how our initial code looks.

  1. class JsonParser {  
  2.     private string JsonData { getset; }  
  3.     public JsonParser(string jsonData) {  
  4.         JsonData = jsonData;  
  5.     }  
  6.   
  7.     public object Parse() {  
  8.         WriteLine("Parsing Json Data");  
  9.         // Parsing Logic  
  10.         return new { ParsedData = JsonData };  
  11.     }  
  12. }  
  13.   
  14. class MainClass {  
  15.     static void Main(string[] args) {  
  16.         var jsonParser = new JsonParser("{ 'temp' : '38' }");  
  17.         var parsedData = jsonParser.Parse();  
  18.     }  
  19. }  

 Examples are written in C#, but easily understandable for anyone who knows basic OOPS concepts.

Approach 1 - Separate method for each parser

For each type of parser, separate methods are created in JsonParser Class. Inside Parse(), according to the source of data passed the appropriate method is called.

  1. class JsonParser {  
  2.     private string JsonData { getset; }  
  3.   
  4.     public JsonParser(string jsonData) {  
  5.         JsonData = jsonData;  
  6.     }  
  7.   
  8.     private object ParseSensorData() {  
  9.         WriteLine("Parsing Sensor Json Data");  
  10.         // Logic optimized for parsing Sensor Data  
  11.         return new { ParsedData = JsonData };  
  12.     }  
  13.   
  14.     private object ParseBlogData() {  
  15.         WriteLine("Parsing Blog Json Data");  
  16.         // Logic optimized for parsing Blog Data  
  17.         return new { ParsedData = JsonData };  
  18.     }  
  19.   
  20.     public object Parse(string source) {  
  21.         switch (source) {  
  22.             case "sensor":  
  23.                 return ParseSensorData();  
  24.             case "blog":  
  25.                 return ParseBlogData();  
  26.             default:  
  27.                 throw new Exception("parser not available for given type.");  
  28.         }  
  29.     }  
  30. }  
  31.   
  32. class MainClass {  
  33.     static void Main(string[] args) {  
  34.         var jsonParser = new JsonParser("{ 'temp' : '38' }");  
  35.         var parsedData = jsonParser.Parse("sensor");  
  36.     }  
  37. }   

Reviewing approach 1

Single class has multiple responsibilities, for adding a new parser, JsonParser class and Parse() have to be modified, which is not a good practice.

Approach 2 - Moving parsing logic to Child Classes

JsonParser class is made abstract with abstract Parse(), SensorDataParser and BlogDataParser  implementing Parse(). While consuming, the user can decide which Parser object to initialize.

  1. public abstract class JsonParser {  
  2.     public string JsonData;  
  3.   
  4.     public JsonParser(string jsonData) {  
  5.         JsonData = jsonData;  
  6.     }  
  7.   
  8.     public abstract object Parse();  
  9. }  
  10.   
  11. class SensorDataParser : JsonParser {  
  12.     public SensorDataParser(string jsonData) : base(jsonData) {  
  13.     }  
  14.   
  15.     public override object Parse() {  
  16.         WriteLine("Parsing Sensor Json Data");  
  17.         // Logic optimized for parsing Sensor Data  
  18.         return new { ParsedData = JsonData };  
  19.     }  
  20. }  
  21.   
  22. class BlogDataParser : JsonParser {  
  23.     public BlogDataParser(string jsonData) : base(jsonData) {  
  24.     }  
  25.   
  26.     public override object Parse() {  
  27.         WriteLine("Parsing Blog Json Data");  
  28.         // Logic optimized for parsing Blog Data  
  29.         return new { ParsedData = JsonData };  
  30.     }  
  31. }  
  32.   
  33. class MainClass {  
  34.     static void Main(string[] args) {  
  35.         JsonParser jsonParser = new SensorDataParser(  
  36.             "{ 'temp' : '38' }");  
  37.         var parsedData = jsonParser.Parse();  
  38.   
  39.         jsonParser = new SensorDataParser(  
  40.             "{ 'title' : 'Strategy Design Pattern by Example' }");  
  41.         parsedData = jsonParser.Parse();  
  42.     }  
  43. }  

Reviewing approach 2

Parsing logic is tightly coupled with parser class. For each type for which we need to write whole parser classand write only logic not whole class, it can be made more pluggable.

Approach 3 - Making more pluggable with interface

Instead of making JsonParser class abstract we will have a proprty of type IJsonParseLogic, classes implementing this interface can be passed before calling Parse(). Here you can see BlogDataParseLogic& SensorDataParseLogic have implemented IJsonParseLogic and their object is set in JsonParseLogic in Main().

Source Code GitHub

Strategy Pattern / JsonParser / Approach3

  1. class JsonParser {  
  2.     public IJsonParseLogic JsonParseLogic { getset; }  
  3.     private string JsonData { getset; }  
  4.   
  5.     public JsonParser(string jsonData) {  
  6.         JsonData = jsonData;  
  7.     }  
  8.   
  9.     public object Parse() {  
  10.         return JsonParseLogic.Parse(JsonData);  
  11.     }  
  12. }  
  13.   
  14. interface IJsonParseLogic {  
  15.     object Parse(string jsonData);  
  16. }  
  17.   
  18. class BlogDataParseLogic : IJsonParseLogic {  
  19.     public object Parse(string jsonData) {  
  20.         WriteLine("Parsing Blog Json Data");  
  21.         // Logic optimized for parsing Blog Data  
  22.         return new { ParsedData = jsonData };  
  23.     }  
  24. }  
  25.   
  26. class SensorDataParseLogic : IJsonParseLogic {  
  27.     public object Parse(string jsonData) {  
  28.         WriteLine("Parsing Sensor Json Data");  
  29.         // Logic optimized for parsing Sensor Data  
  30.         return new { ParsedData = jsonData };  
  31.     }  
  32. }  
  33.   
  34. class MainClass {  
  35.     static void Main(string[] args) {  
  36.         JsonParser jsonParser = new JsonParser(  
  37.             "{ 'temp' : '38' }");  
  38.         jsonParser.JsonParseLogic = new SensorDataParseLogic();  
  39.         var parsedData = jsonParser.Parse();  
  40.   
  41.         jsonParser = new JsonParser(  
  42.             "{ 'title' : 'Strategy Design Pattern by Example' }");  
  43.         jsonParser.JsonParseLogic = new BlogDataParseLogic();  
  44.         parsedData = jsonParser.Parse();  
  45.     }  
  46. }  

Conclusion

As you can see in approach 3 algo/logic is decoupled, which makes the code more maintainable. In scenarios like this Strategy Pattern can be used.

Complete Source Code

Strategy Pattern / JsonParser