Flyweight design pattern for beginners.

Problem statement –

Flyweight is one of the complex design patterns. Understanding it will be comfortable through a proper, step-by-step example. Be patient and try to follow the below-mentioned concept. We will understand the problem statement of the Flyweight design pattern completely through text editor example.

Try to develop a text editor. You will learn many design patterns from text editor application implementation. Think about a text editor page. As per standards, a line contains 80 alphabets, and a page contains 30 lines. Let us model a text editor page. It is nothing but a row and column base structure, like matrix, which can hold one character at a time. In any particulate location. 


The smallest structure/unit of text editor is alphabet (a,b,c, .., z). One location will contain one alphabet, which may have its own font, size, color, style, etc. If I consider those smallest units as objects, represented at a particular location on text editor. We will end up with 80*30 ( = 2400) objects per page. This is not ending, think about some necessary use cases –
1.     Scenario when you scroll the page up and down, all objects will be destroyed, and new objects will be loaded in the editor.
2.     You will have 40 unique characters (@,#,$, etc.) to handle.
3.      0-9 digits. So, 10 more objects.

So many objects, our OS will have limited resources, and this approach is not right, because it will try to consume all the RAM space, which is not a good design. 

This is one of the best examples where we may end up having so many objects. How to fix it?

The problem is vast, now the concept of object sharing comes into picture. As many objects will repeat very frequently. For example, a word, i.e., “e”, will go so many times while typing. Can’t I create a system with object sharing? Then we will end up having only –
1.       26 alphabets.
2.       0-9 digits and 40 unique characters.

Now we will end up having approx 100 sharing objects; which sounds good compared to the previous number, more than 2400.

Understand the concept of editor; for each location on the editor page (in matrix), we will put one alphabet with – size, font, color, lower, upper, etc. Imagine a structure or class having these many properties set per object. Once we set all the mentioned property for any single location. We will move on to the next location, we will clean the old created object and reuse it next location.

See it through a step by step example. In this example, I am typing "pattern" world on my text editor - 

First, Empty page. 
  • Right now my data pool is empty -

  • My page is also empty.

Second, at this stage, I am inserting the first alphabet 'P' through the keyboard.

  • Flyweight factory will create object P, and my data pool will save object P; with default properties {lowerCase = false, color black, size 10, someFont, UpperCase = true} -
  • My editor will have first letter P on the screen -

Third, enter 'A',
  • Flyweight factory will create object A, and my data pool will save object A with default properties {lowerCase = false, color black, size 10, someFont, UpperCase = true} -
  • A will appear on editor screen -
Fourth, enter 'T',
  • Flyweight factory will create object T, and my data pool will save object T with default properties {lowerCase = false, color black, size 10, someFont, UpperCase = true} -  
  • Letter T will appear on editor screen - 
Fifth, enter 'T', again,
  • This time Flyweight factory will reuse object T, from my data pool, as it has been already created and saved on data pool memory - 
  • Again letter T will appear on the screen without creating object T on memory.
Sixth, enter 'E',
  • Flyweight factory will create object E, and my data pool will save object E with default properties {lowerCase = false, color black, size 10, someFont, UpperCase = true} - 
  • E will appear on the screen -
Seventh, enter 'r' and 'N'
  • Flyweight factory will create object P and R. My data pool will save object P and R with default properties {lowerCase = false, color black, size 10, someFont, UpperCase = true}
  • E will appear on the screen -
Finally, we are talking about sharing objects. There are some rules when we can share the objects –

  1. When an object is not having its own internal stage. You can share them across the system, which is not possible practically, as the object will have its internal stage.
  2. If the internal state of the objects are the same.
  3. If, at all, they have uncommon state (internal data members), we have some mechanisms of extracting whatever is not common.
  4. The cost of externalization should be less than the memory saving cost. Otherwise, no use of object sharing.

"When there is a huge amount of objects, and those objects are repeating in a very high frequency. Then we can use flyweight design to solve this problem."

Hoping by now, you have understood the problem statement completely.

Why we need flyweight design pattern –


It will help us to reuse the created object in very efficient manner.

Let’s take one more but small example, front end applications do talk to the database (DB), and we create DB connection objects. We have some limitations on creating connection objects. For example, 20 at a time. We will share those 20 objects among all requests, instead of creating one object per request and destroying it after work done. We can solve it through the flyweight design pattern. Creation and destruction will also cost a lot, for heavy objects, and here we are saving those costs as well.

Flyweight design pattern –


We do have a factory associated with the flyweight design pattern; Which will manage the objects. It is beneficial when you are dealing with a massive number of repeating objects.

Structure –

flyweight Dp.
UML of flyweight design pattern.

Example -



flyweight DP Ex
Example of flyweight design pattern (Click on picture for zoomed view).



/**************************************************************************************
/* Suppose you want to see random images.
/* If we dont maintain a cache we will end up loading same image again and again. 
/* best option is load one image from file, and keep it in memory, if user demands to
/* see same image again then no need to look into file syatem again just display 
/* from cached memory.
***************************************************************************************/

#include <iostream>
#include <map>
#include <string>
using namespace std;

enum Type
{
    CAR,
    BIKE
};


class IImages
{
    public:
        virtual string getImage(string name) = 0;
        //virtual void getImage() = 0;
};

class BikeImages : public IImages
{
    public:
        BikeImages()
        {
            m_bikeDictionary.insert( pair <string,string> ("Pulsar","Bajaj Pulsar"));
            m_bikeDictionary.insert( pair <string,string> ("Splendor","Hero Splendor"));
            m_bikeDictionary.insert( pair <string,string> ("Discover","Bajaj Discover"));
            m_bikeDictionary.insert( pair <string,string> ("City","TVS Star City Plus"));
            m_bikeDictionary.insert( pair <string,string> ("CBZ","Hero CBZ"));
            m_bikeDictionary.insert( pair <string,string> ("Karizma","Hero Karizma"));
            m_bikeDictionary.insert( pair <string,string> ("Platina","Bajaj Platina"));
            m_bikeDictionary.insert( pair <string,string> ("Shine","Honda Shine"));
            m_bikeDictionary.insert( pair <string,string> ("Stunner","Honda CBF Stunner"));
            m_bikeDictionary.insert( pair <string,string> ("Fazer","Yamaha Fazer"));
        }
        
        string getImage(string name)
        {
            if(m_bikeDictionary.find(name) != m_bikeDictionary.end())
            {
                return ((m_bikeDictionary.find(name))->second);
            }
        }
    private:
        map<string,string> m_bikeDictionary;
};

class CarImages : public IImages
{
    public:
        CarImages()
        {
            m_carDictionary.insert( pair <string,string> ("Fit","Honda Fit"));
            m_carDictionary.insert( pair <string,string> ("Impreza","Subaru Impreza"));
            m_carDictionary.insert( pair <string,string> ("Camry","Toyota Camry"));
            m_carDictionary.insert( pair <string,string> ("Forester","Subaru Forester"));
            m_carDictionary.insert( pair <string,string> ("RX","Lexus RX"));
            m_carDictionary.insert( pair <string,string> ("MX-5","Mazda MX-5 Miata"));
            m_carDictionary.insert( pair <string,string> ("Impala","Chevrolet Impala"));
            m_carDictionary.insert( pair <string,string> ("Sorento","Kia Sorento"));
            m_carDictionary.insert( pair <string,string> ("F-150","Ford F-150"));
            m_carDictionary.insert( pair <string,string> ("Sienna","Toyota Sienna"));
        }
        string getImage(string name)
        {
            if(m_carDictionary.find(name) != m_carDictionary.end())
            {
                return ((m_carDictionary.find(name))->second);
            }
        }
    private:
        map<string,string> m_carDictionary;
};
//*************************************************************************************
// Class ImageLoader will act as Flyweight factory class.And maintain one local 
// dictionary to cache image. 
class ImageLoader 
{
    public:
        ImageLoader():m_bikeImages(NULL),m_carImages(NULL) {}
        
        void showImage(string name, Type type)
        {
            string tempImageHolder;
            if(type == CAR)
            {
                if(m_carImages == NULL)
                {
                    m_carImages = new CarImages;
                }
                if(m_imageDictionary.find(name) != m_imageDictionary.end())
                {
                    cout<<"found the image in local cache."<<endl;
                    cout<<"Car image --> "<<(m_imageDictionary.find(name))->second<<endl;
                    return;
                }
                tempImageHolder = m_carImages->getImage(name);
                cout<<"Car image --> "<<tempImageHolder<<endl;
                loadImage(name,m_carImages->getImage(name));
            }
            else
            {
                if(m_bikeImages == NULL)
                {
                    m_bikeImages = new BikeImages;
                }
                if(m_imageDictionary.find(name) != m_imageDictionary.end())
                {
                    cout<<"found the image in local cache."<<endl;
                    cout<<"Car image --> "<<(m_imageDictionary.find(name))->second<<endl;
                    return;
                }
                tempImageHolder = m_bikeImages->getImage(name);
                cout<<"Bike image --> "<<tempImageHolder<<endl;
                loadImage(name,m_bikeImages->getImage(name));
            }
        }
        
        void loadImage(string name, string image)
        {
            m_imageDictionary.insert(pair<string,string> (name,image));
        }
    private:
        map<string,string> m_imageDictionary;
        BikeImages* m_bikeImages;
        CarImages* m_carImages;
};

/*
#######################################################################################
#########  __  __       _        ######################################################
######### |  \/  | __ _(_)_ __   ######################################################
######### | |\/| |/ _` | | '_ \  ######################################################
######### | |  | | (_| | | | | | ######################################################
######### |_|  |_|\__,_|_|_| |_| ######################################################
#######################################################################################
*/
int main(int argc, char** argv)
{
    ImageLoader imageLoaderObj;
    Type imageType;
    imageType = CAR;
    imageLoaderObj.showImage("Fit",imageType);
    imageLoaderObj.showImage("Impreza",imageType);
    imageLoaderObj.showImage("Camry",imageType);
    imageLoaderObj.showImage("Fit",imageType);
    imageLoaderObj.showImage("Camry",imageType);
    imageType = BIKE;
    imageLoaderObj.showImage("Pulsar",imageType);
    imageLoaderObj.showImage("Splendor",imageType);
    imageLoaderObj.showImage("Discover",imageType);
    imageLoaderObj.showImage("Pulsar",imageType);
    imageLoaderObj.showImage("Discover",imageType);
    return 0;
}


///////////////////////////////////////////////////////////////////-------------OutPut
/*
$ ./a.exe
Car image --> Honda Fit
Car image --> Subaru Impreza
Car image --> Toyota Camry
found the image in local cache.
Car image --> Honda Fit
found the image in local cache.
Car image --> Toyota Camry
Bike image --> Bajaj Pulsar
Bike image --> Hero Splendor
Bike image --> Bajaj Discover
found the image in local cache.
Car image --> Bajaj Pulsar
found the image in local cache.
Car image --> Bajaj Discover

*/




Example 2 -




 #include<iostream>
 #include<string>
 #include<map>
 using namespace std;

 class Alphabets 
 {
 public:
  virtual void draw(int fontSize) = 0;
  
 protected:
  char m_symbol;
  int m_fontSize;
  string m_color;
  bool m_upperCase;
  bool m_lowercase;
  int m_position;
 
 };

 class AlphabetA : public Alphabets
 {
  public:
  // setting by default values.
  AlphabetA()
  {
   m_symbol = 'a';
   m_fontSize = 10;
   m_color = "black";
   m_upperCase = false;
   m_lowercase = true;
   m_position = 0;
  }  
  
  void draw(int fontSize)
  {
  cout<< "Displaying Alphabet - " <<m_symbol << "at " << m_position << "position"<<endl;
   
  }
 };

 class AlphabetB : public Alphabets
 {
  public:
  // setting by default values.
  AlphabetB()
  {
   m_symbol = 'b';
   m_fontSize = 10;
   m_color = "black";
   m_upperCase = false;
   m_lowercase = true;
   m_position = 0;
  }
  
  void draw(int fontSize)
  {
  cout<< "Displaying Alphabet - " <<m_symbol << "at " << m_position << "position"<<endl;
   
  } 
 };

 class AlphabetC : public Alphabets
 {
  public:
  // setting by default values.
  AlphabetC()
  {
   m_symbol = 'b';
   m_fontSize = 10;
   m_color = "black";
   m_upperCase = false;
   m_lowercase = true;
   m_position = 0;
  }
  
  void draw(int fontSize)
  {
  cout<< "Displaying Alphabet - " <<m_symbol << "at " << m_position << "position"<<endl;
   
  }
 };
 //----------this will keep on going and we will implement it till Z and all different kind
 //     of special symbols.

 class FlyweightFactory
 {
  public:
  FlyweightFactory(){}
  
  ~FlyweightFactory()
  {
   while(!m_dictionary.empty())
   {
    map<char,Alphabets*>::iterator it = m_dictionary.begin();
    delete it->second;
    m_dictionary.erase(it);
   }
  }
  
  Alphabets* getAlphabet(char key)
  {
   Alphabets* newAlphabetsPointer = NULL;
   
   if(m_dictionary.find(key) != m_dictionary.end())
   {
    cout<< "Alphabet "<< key << " already exist in memory pool"<<endl;
    newAlphabetsPointer = m_dictionary[key];
   }
   else
   {
    switch(key)
    {
     case 'a':
      newAlphabetsPointer = new AlphabetC;
      break;
     case 'b':
      newAlphabetsPointer = new AlphabetB;
      break;
     case 'c':
      newAlphabetsPointer = new AlphabetC;
      break;
     default:
      cout<<"this alphabet is not implemented yet"<< endl;
      return newAlphabetsPointer;
    }
    m_dictionary[key] = newAlphabetsPointer;
   }
   return newAlphabetsPointer;
  }
  private:
  map<char,Alphabets*> m_dictionary;
 };

 int main(int argc, char** argv)
 {
  string input = "aabbccd";
  const char* alphabets = input.c_str();
  FlyweightFactory flyweightFactoryObject;
  int startingSize = 10;
  
  for (size_t index = 0; index < input.length(); ++index)
  {
  Alphabets* alphabetsPointer = flyweightFactoryObject.getAlphabet(alphabets[index]);
  if(alphabetsPointer)
   alphabetsPointer->draw(startingSize);
  ++startingSize;
  }
  
  return 0;
 }

 
 ///////////////////////////////////////////////////////////////////-------------OutPut
 /*
 $ ./a.exe
 Displaying Alphabet - bat 0position
 Alphabet a already exist in memory pool
 Displaying Alphabet - bat 0position
 Displaying Alphabet - bat 0position
 Alphabet b already exist in memory pool
 Displaying Alphabet - bat 0position
 Displaying Alphabet - bat 0position
 Alphabet c already exist in memory pool
 Displaying Alphabet - bat 0position
 this alphabet is not implemented yet

 */



Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page.

Comments

Popular posts from this blog

Non-virtual interface idiom (NVI)

Factory method design pattern for beginners.

Architectural patterns => Mud to structure => layers.