Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / CSS

Comparison of Template and Strategy Design Patterns

4.84/5 (6 votes)
12 Dec 2013CPOL7 min read 42.7K   128  
comparison of template and strategy design pattern from some of my work experience

Introduction

In this article I have demonstrated the comparison between template and strategy design pattern with the help of a code example from my work. By the end of this article reader will understand the difference and similarities between the template method and strategy design pattern. In the start I will describe the problem or requirement of the software feature which I have to implement, afterword I will describe what was my initial solution and the problems to that solution that arises later on. After that I describe how I resolve the problems to my first solution.

The initial problem that I was facing

One of the feature requirements for the data processing software on which I was working is to convert data files from several other systems to our local system. There are 4 other or outer systems which generate some data which is little different than our data format. I can’t control those because they are outer system. Therefore I have got 4 different files from 4 different systems needed to convert to a file which is readable by our own system.

My initial solution 

After careful analysis of these outer system files I come to realize that they are some similarities and some differences. Hence I develop a general conversion algorithm and use template method.

Template method:  

Briefly a template method design pattern holds general algorithm in a method and let the subclasses to define some specific steps of that algorithm. In this way a general algorithm can be applied to many situations by sub-classing for each individual situation or case.

In this pattern the class which contains the algorithm must be abstract; this class should make some steps of the algorithm to be abstract so that the subclass can override them.

Image 1

In the above diagram TemplateMethod() contains the steps of the algorithm whereas abstractoperation1() and asbtractionoperation2() are some specific steps inside the algorithm but they will be implemented by the ConcreteClass1 but not the AbstractClass. Therefore we can extend this generic algorithm in many ways.

Let’s discuss my initial solution based on this template method. As I discussed earlier I have develop a general algorithm data conversion its steps are given below:

Algorithm steps

Step 1: Read lines from outer system files.
Step 2: Processed Each String Line and Convert them to bits array (bool array)
Step 2(a) Format Each Line before converting to bits array

Step 2(a) (a) Process each element in the line.
Step 3: Convert Each Bool Array(Bits) to a Byte Array
Step 4: Write bytes array to a file.

I will have to apply this algorithm repeatedly on each of the external system files to convert them to my local system file. For that I need to change this generic algorithm for each system file since each file is different than the other.

After careful analysis of input files I realize that only step 2 will be changed for each file whereas step 1, 3 and 4 will remain constant for each input file.

This looks like a perfect situation to apply template method design pattern. I even deep dive into the step 2 and factor the step 2 into two more steps. In each four files I only need to change these 2 steps of step 2. Now I can 2 algorithm steps as abstract and allow subclasses to implement them. Following is the class diagram of my initial solution.

Image 2

one of the sub-class implementation is given below:

C#
class DecimalFrameSyncEndDataConverter: AbstractDataConverter
{
    protected override short ParseEachElementInLine(string[] strByteArray, int j)
    {
        short ashort = short.Parse(strByteArray[j]);
        return ashort;
    }
 
    protected override void FormatEachLineBeforeProcessing(string[] strByteArray)
    { 
        string FS1 = strByteArray[47];
        string FS2 = strByteArray[48];
 

        for (int strByteCounter = 48; strByteCounter >= 3; strByteCounter--)
        {
            strByteArray[strByteCounter] = strByteArray[strByteCounter - 2];
        }

        strByteArray[1] = FS1;
        strByteArray[2] = FS2;
    } 
} 

Hence this looks like a solid approach since many of the code is shared among the concrete classes and the conversion was working beautifully.

Why the problem arose? When there was a completely new file type...

One famous quotation for software development is “only thing that remain constant is change”. After a while it exposed upon me that there was another system which customers want to incorporate. That outer system generates the output file in a completely different than the previous one.
After careful analysis I find out that I need to change the all the steps of the algorithm and for that I need to alter the abstract class. This a bad thing from design point of view because classed should be closed for modification but open for extension. As well as there can be other outer system which will one day be exposed to me and asked to develop a code for conversion of data. Therefore I needed another approach for designing my classes.

How do I handle this new problem?

In order to deal with the solution I used the strategy design pattern. This pattern is closely related to template method. But it gives more flexibility.

Strategy Pattern:

Strategy pattern encapsulate the behavior of an object. Strategy pattern also allows us to define a set of algorithms. Each class defines its own algorithm whose steps can be entirely different from each other.

I used this pattern because each algorithm can be extremely different than the others. This is very good for my design problems because now I can handle different data files for conversion which can be based on completely different algorithms. Following is the class diagram of the implementation

Here is the simple text book diagram for the strategy

Image 3

Now my implementation for of the solution using strategy. Here is the class diagram:

Image 4

Code for one of the classes in the above previous diagram

C#
class DecimalFrameSyncEndDataConverter : IDataConverter
{
    #region ILOCAdapter Members
 
    public void ConvertData(string fileAddress)
    {
        // Step 1: 
        // Read file:
                  
        FileStream fs = new FileStream(fileAddress, FileMode.Open);
 
        StreamReader reader = new StreamReader(fs);
           
        List<string> allLines = new List<string>();
 
        while (!reader.EndOfStream)
        {
                string aLine = reader.ReadLine();
                allLines.Add(aLine);
                        
        }
        reader.Close();
 

        //Step: 2
        //Processed Each String Line and Convert them to bool arrays.
        List<byte> lstByteArray = new List<byte>();
 
        // List<short> lstShortArray = new List<short>();

        String[] strByteArray = new string[100];
        short[] aShortArray = new short[10];
        aShortArray[0] = 1;
        aShortArray[1] = 2;
        aShortArray[2] = 4;
        aShortArray[3] = 8;
        aShortArray[4] = 16;
        aShortArray[5] = 32;
        aShortArray[6] = 64;
        aShortArray[7] = 128;
        aShortArray[8] = 256;
        aShortArray[9] = 512;
        List<bool[]> allBitArrays = new List<bool[]>();
        for (int i = 0; i < allLines.Count; i++)
        {
            //string aChar = "     ";
            string[] separatingCharacters = { " " };
 
            strByteArray = allLines[i].Split(separatingCharacters, 100, StringSplitOptions.RemoveEmptyEntries);
 
            // place the frame synce at the start if it is coming at END..
            // right Shift the complete array...

            if (strByteArray.Length >= 49)
            {
               string FS1 = strByteArray[47];
               string FS2 = strByteArray[48];
 
               for (int strByteCounter = 48; strByteCounter >= 3; strByteCounter--)
               {
                   strByteArray[strByteCounter] = strByteArray[strByteCounter - 2];
 
               }
 
               strByteArray[1] = FS1;
               strByteArray[2] = FS2;
               bool[] aBitArray = new bool[480];
               int aBitArrayCount = 479;
 
                for (int j = 1; j < strByteArray.Length; j++)
                {
 
                    //lstByteArray.Add(byte.Parse(strByteArray[j]));
                    short ashort = short.Parse(strByteArray[j]);
                
                    bool[] shortBool = new bool[10];
 
                    for (int shortCount = 0; shortCount < aShortArray.Length; shortCount++)
                    {
                        short aBoolResult = (short)(aShortArray[shortCount] & ashort);
 
                        if (Convert.ToBoolean(aBoolResult))
                        {
                            shortBool[shortCount] = true;
                        }
                            else
                            {
                                shortBool[shortCount] = false;
                            }
                        }
                        for (int shortBoolCount = shortBool.Length - 1; shortBoolCount >= 0; shortBoolCount--)
                        {
                            aBitArray[aBitArrayCount] = shortBool[shortBoolCount];
                            aBitArrayCount -= 1;
                        }
                    }
                    allBitArrays.Add(aBitArray);
 
                }
            }

            //step 3:
            //Convert Each Bool Array(Bits) to a Byte Array
            byte[] aReferenceBoolByte = new byte[8];
 
            aReferenceBoolByte[0] = 1;
            aReferenceBoolByte[1] = 2;
            aReferenceBoolByte[2] = 4;
            aReferenceBoolByte[3] = 8;
            aReferenceBoolByte[4] = 16;
            aReferenceBoolByte[5] = 32;
            aReferenceBoolByte[6] = 64;
            aReferenceBoolByte[7] = 128;
            List<byte> allBytesToWrite = new List<byte>();
            for (int allBitArrayCount = 0; allBitArrayCount < allBitArrays.Count - 1; allBitArrayCount++)
            {
                bool[] allBitInArray = allBitArrays[allBitArrayCount];
                int allBitInArrayCount = 479;
                bool[] myByteBoolArray = new bool[8];
                while (allBitInArrayCount >= 0)
                {
                    for (int readCount = 7; readCount >= 0; readCount--)
                    {
                        myByteBoolArray[readCount] = allBitInArray[allBitInArrayCount];
                        allBitInArrayCount--;
                    }
                    byte b = 0x00;
 
                    for (int myIntCount = 0; myIntCount < 8; myIntCount++)
                    {
                        if (myByteBoolArray[myIntCount])
                        {
                            b = (byte)(b | aReferenceBoolByte[myIntCount]);
                        }
                        else
                        {
                           b = (byte)(b | 0x00);
                        }
                    }
                    allBytesToWrite.Add(b);
 
                }
            }
 
            //Step 4
            // Write bytes array to a file.
            FileStream fStream = new FileStream(@"C:\s2\LOCTest.DAT", FileMode.Create);
            BinaryWriter bWriter = new BinaryWriter(fStream);
 
            foreach (byte b in allBytesToWrite)
            {
                bWriter.Write(b);
            }
            fStream.Close();
            bWriter.Close();
            // return the report..
        }
 
        #endregion
    }

and now the code of the new class which is BinarDataConverter.cs

C#
class BinaryDataConverter : IDataConverter
{
    public void ConvertData(string fileAddress)
    {
        // find all major frames...
        FileStream fs = new FileStream(fileAddress, FileMode.Open);
        BinaryReader br = new BinaryReader(fs);
        byte[] buffer = new byte[br.BaseStream.Length];
        br.Read(buffer, 0, buffer.Length);
         br.Close();
        fs.Close();
        List<byte> fileData = new List<byte>();
        fileData.AddRange(buffer);
        short[] aShortArray = new short[10];
        aShortArray[0] = 1;
        aShortArray[1] = 2;
        aShortArray[2] = 4;
        aShortArray[3] = 8;
        aShortArray[4] = 16;
        aShortArray[5] = 32;
        aShortArray[6] = 64;
        aShortArray[7] = 128;
        aShortArray[8] = 256;
        aShortArray[9] = 512;
       
        List<bool[]> allBitArrays = new List<bool[]>();
          
        List<int> subFrameLocations = SearchConstPattern(fileData, new byte[] { 0x19, 0xFF, 0x22, 0x42 });
        for (int majCount = 0; majCount < subFrameLocations.Count; majCount++)
        {
 
            byte[] aByteArray = new byte[104];
 
            int position = subFrameLocations[majCount];
 
            fileData.CopyTo(subFrameLocations[majCount] , aByteArray, 0, 104);
            int aBitArrayCount = 479;
          
 
                bool[] aBitArray = new bool[480];
                // now retreive the values from each sub frame..
                for (int i = 1; i < aByteArray.Length; i += 2)
                {
                    // we want to skip extra data present in LOC format..
                    if (i >= 4 && i <= 11)
                        continue;
                    
           // bringing the data in short format (2 byte data) is same across all 
           // some time we bring from string to short.. but in this we have binary data 
                    short shortValue = 0x0000;
                    shortValue = (short)(shortValue | aByteArray[i]);
                    shortValue = (short)(shortValue << 8);
                    shortValue = (short)(shortValue | aByteArray[i - 1]);
 
                    // convert that shortValue to bool value..

                    bool[] shortBool = new bool[10];
 
                for (int shortCount = 0; shortCount < aShortArray.Length; shortCount++)
                    {
 
                     short aBoolResult = (short)(aShortArray[shortCount] & shortValue);
 
                        if (Convert.ToBoolean(aBoolResult))
                        {
                            shortBool[shortCount] = true;
                        }
                        else
                        {
                            shortBool[shortCount] = false;
                        }
 
                    }
 
            // we are putting bool from the end .. it will be retreive from the end..

                for (int shortBoolCount = shortBool.Length - 1; shortBoolCount >= 0; shortBoolCount--)
                    {
                        aBitArray[aBitArrayCount] = shortBool[shortBoolCount];
                        aBitArrayCount -= 1;
                    }
                }
 
            allBitArrays.Add(aBitArray);
       }
        // from bit arrays we convert the data to binary format.. this is same across all files..
        byte[] aReferenceBoolByte = new byte[8];
        aReferenceBoolByte[0] = 1;
        aReferenceBoolByte[1] = 2;
        aReferenceBoolByte[2] = 4;
        aReferenceBoolByte[3] = 8;
        aReferenceBoolByte[4] = 16;
        aReferenceBoolByte[5] = 32;
        aReferenceBoolByte[6] = 64;
        aReferenceBoolByte[7] = 128;
        List<byte> allBytesToWrite = new List<byte>();
 
        for (int allBitArrayCount = 0; allBitArrayCount < allBitArrays.Count - 1; allBitArrayCount++)
        {
            bool[] allBitInArray = allBitArrays[allBitArrayCount];
            int allBitInArrayCount = 479;
 
            bool[] myByteBoolArray = new bool[8];
 
            while (allBitInArrayCount >= 0)
            {
                for (int readCount = 7; readCount >= 0; readCount--)
                {
                    myByteBoolArray[readCount] = allBitInArray[allBitInArrayCount];
                    allBitInArrayCount--;
                }
                byte b = 0x00;
 
                for (int myIntCount = 0; myIntCount < 8; myIntCount++)
                {
                    if (myByteBoolArray[myIntCount])
                    {
                        b = (byte)(b | aReferenceBoolByte[myIntCount]);
                    }
                    else
                    {
                        b = (byte)(b | 0x00);
 
                   }
                }
                allBytesToWrite.Add(b);
            }
  
        }
        // write binary data
        FileStream fStream = new FileStream(@"C:\LOCTest.DAT", FileMode.Create);
        BinaryWriter bWriter = new BinaryWriter(fStream);
 
        foreach (byte b in allBytesToWrite)
        {
            bWriter.Write(b);
 
        }
        fStream.Close();
        bWriter.Close();
        // return the report..
     
    }
   private List<int> SearchConstPattern(List<byte> ncFrame4, byte[] pattern){.. }
   private int FindPattern(byte[] data, byte[] pattern){.. }
   private int[] ComputeFailure(byte[] pattern){ ...}
}

As one can see I have to repeat a lot of code in each of the implementing classes.

Benefit with this in context of Strategy Pattern

As one can see implementing classes also depend upon the template method class. This dependency causes to change the template method if one wants to change some of the steps of the algorithm. On the other side strategy completely encapsulates the algorithm. it gives the implementing classes to completely define an algorithm. Therefore if any change arrives one does need to change the code for previously written classes. This was the primary reason I choose strategy for designing up the classes.

One feature of template method is that template method controls the algorithm. Which can be a good thing in other situation but in my problem this was restricting me to design the classes. On the other side strategy does not control the steps of an algorithm which enables me to add completely different conversion methods. Hence in my case strategy helps me for implementation.

One drawback of strategy is that there is too much code redundancy and less code sharing. As it is obvious in the presented example of this article I have to repeat the same code in four classes again and again. Therefore it is hard to maintain because if the implementation of our system such as step 4 which is common to all is changed then I will have to update this in all 5 classes. On the other side, in template method, I can only change the superclass and the changes are reflected into the sub classes. Therefore template method gives a very low amount of redundancy and high amount of code sharing among the classes.

Strategy also allows changing the algorithm at run-time. In template method one will have to re-initialize the object. This feature of strategy provide large amount of flexibility. From design point of view one has to prefer composition over inheritance. Therefore using strategy pattern also became the primary choice for development.

Conclusion

There is no right or wrong design pattern. It depends upon the problem one is facing, why I choose strategy because in my case it was more helpful because of low dependency. In this article I have given snapshot of the code for a feature. This feature belongs to large data processing software. I have explained the requirements of the customer. I explained design of my initial solution then how the requirements of the user changes and then I explained the updated design. This is a good example to learn some similarities and difference of the template method pattern and strategy pattern.

References

  • (1)Book "Design Patterns: Elements of Reusable Object-oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides .
  • (2)Book "Head First Design Patterns" by Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra and Elisabeth Robson.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)