Design patterns: Flyweight

in #design-patterns7 years ago

Flyweight-Pattern-500x333.jpg

It’s time for another pattern, and more precisely for the Flyweight pattern, whose purpose is, in a nutshell, to limit the memory occupied by many objects, further on the article about the pattern.

Intent

  1. As much as possible reduction used memory wasted for servicing many similar objects.
  2. Replacing so-called heavy objects on light objects.
  3. The use of object sharing for effective management of many objects, i.e. we do not create every object from the beginning, we only base on already created objects thanks to this we increase the application speed.

Problem

The Flyweight pattern is eg used in browsers as a cache, i.e. if a user enters a page, he reads it from scratch but if the user goes back to the same page, it will load faster because repetitive page elements, eg text, pictures has been saved after the first page view to the browser cache, so the browser does not have to re-load the page elements from scratch only from the cache where the page elements are already saved 🙂

Another example is the use of a text document, in which we assume that there are a thousand text characters, each of these characters is not created from scratch, for example if we want to enter a character, we check if the same character is already in this document and create his light object earlier backup, thanks to everything works faster and we have more memory to use.

Use when:

  1. You create an application that uses a large number of repeating objects.
  2. Your objects are expensive to store.
  3. The state of the object you create can be saved outside it, eg when you have 100 objects and if in each is color field, then you can solve it in such a way that you create one object (or more if there are four types of colors in it 🌈) from zero and color data are shared with the remaining 99 objects, this is a great saving in memory and performance, because we do not have to create these objects from scratch each time.
  4. In your application, the identity of objects does not matter, i.e. all operations can be performed on copies of these objects.

Structure

Below is the UML diagram of the Flyweight pattern, which shows some interesting things, among others, that often the pattern of the abstraction factory connects with Flyweight, eg we check if any object is in the document if it does, we create instances of this object that is separated into an abstract factory. We also see that the object is created when we need it, we do not create all objects immediately, and that the factory has the function of caching, ie it checks does the same objects are in the application if they does, they create other similar objects based on the data of other objects stored in the factory.

Flyweight1.png

Let’s see an example:

Flyweight_1.png

The Locust, Ant and Cockroach classes can be replaced with light classes because they are not encapsulated and some of them must be completed by the customer and are similar to each other here again, the factory plays caching functions.

Real-life examples

Saving characters in a document

Let’s see how it looks more or less in practice, or rather it should look like writing characters in a document eg txt, let’s start with an abstract main class.

To Char’s abstract class we write characters and classes that inherit from it, this classes create character objects, it may seem that there are a lot of them, but really there are very few compared to how many characters can be in a document or chain, we also have internal state in the Char class, which is a variable of characters to which we save and display them. It is below:

abstract class Char
{
    //internal state
    protected char @char;
 
    public Char(char @char)
    {
        this.@char = @char;
    }
 
    public void DisplayText()
    {
        Console.Write(@char);
    }
}

Now let’s see what happens in the factory class of the Flyweight pattern.

In the method of which we pass characters, ie GetChar, we check whether in the dictionary we defined at the beginning of the class there is no the same character if it is, we only write the character to the variable letter to display it, but we do not create the object again and this is what the Flyweight pattern is all about, we do not create the same object twice and we are sharing data. This is what the factory class looks like:

namespace Flyweight
{
    class CharsFactory
    {
        private readonly Dictionary<char,Char> chars=new Dictionary<char,Char>();
        Char letter = null;
        public int NumberRepeatingChars;
 
        public Char GetChar(char GiveChar)
        {
            
            if (chars.Keys.Contains(GiveChar))
            {
                letter = chars[GiveChar];
                NumberRepeatingChars++;
            }
            else
            {
                switch (GiveChar)
                {
                    case 'P':
                        letter = new CharP();
                        break;
                    case 'Z':
                        letter = new CharZ();
                        break;
                    case 'R':
                        letter = new CharR();
                        break;
                    case 'K':
                        letter = new CharK();
                        break;
                    case 'L':
                        letter = new CharL();
                        break;
                    case 'A':
                        letter = new CharA();
                        break;
                    case 'D':
                        letter = new CharD();
                        break;
                    case 'O':
                        letter = new CharO();
                        break;
                    case 'W':
                        letter = new CharW();
                        break;
                    case 'T':
                        letter = new CharT();
                        break;
                    case 'E':
                        letter = new CharE();
                        break;
                    case 'S':
                        letter = new CharS();
                        break;
                    case 'Y':
                        letter = new CharY();
                        break;
                    case 'M':
                        letter = new CharM();
                        break;
                    case 'X':
                        letter = new CharX();
                        break;
                    case ' ':
                        letter = new Space();
                        break;
                }
                chars.Add(GiveChar, letter);
            }
            return letter;
        }
    }
}

Then if there is no such a character in the dictionary, then we must create his light weight object and write it to the dictionary, so that later you do not have to create the same object twice and we are saving it to the letter variable to display it. And the classes of light weight objects, in our case signs, look like this:

namespace Flyweight
{
    class CharP : Char
    {
        public CharP() : base('P') { }
    }
    class CharR : Char
    {
        public CharR() : base('R') { }
    }
    class CharZ : Char
    {
        public CharZ() : base('Z') { }
    }
    class CharK : Char
    {
        public CharK() : base('K') { }
    }
    class CharL : Char
    {
        public CharL() : base('L') { }
    }
    class CharA : Char
    {
        public CharA() : base('A') { }
    }
    class CharD : Char
    {
        public CharD() : base('D') { }
    }
    class CharO : Char
    {
        public CharO() : base('O') { }
    }
    class CharW : Char
    {
        public CharW() : base('W') { }
    }
    class CharT : Char
    {
        public CharT() : base('T') { }
    }
    class CharE : Char
    {
        public CharE() : base('E') { }
    }
    class CharS : Char
    {
        public CharS() : base('S') { }
    }
    class CharY : Char
    {
        public CharY() : base('Y') { }
    }
    class CharM : Char
    {
        public CharM() : base('M') { }
    }
    class CharX : Char
    {
        public CharX() : base('X') { }
    }
    class Space : Char
    {
        public Space() : base(' ') { }
    }
}

You may not like the way we create these objects, because we create them using a simple factory, but just such a use of the factory is appropriate because notice that there are a limited number of characters in the english alphabet are only 24 characters, and certainly so many types of characters it will be in a large document and we have to display these characters automatically, we can not choose what characters we want to display because it would take us in a larger project a hundred years. So the fact of using the simple factory for such a problem is okay.

Finally, the customer:

namespace Flyweight
{
    class Program
    {
        static void Main(string[] args)
        {
            string document = "SAMPLE TEXT";
            char[] CharsInDocument = document.ToCharArray();
 
            ConsoleColor[] colors = new ConsoleColor[CharsInDocument.Length];
 
            for (int i = 0; i < CharsInDocument.Length; i++)
            {
                colors[i] = ConsoleColor.White;
                if (i % 2 != 0)
                {
                    colors[i] = ConsoleColor.Green;
                }
            }
 
            CharsFactory CharsFactory = new CharsFactory();
 
            for (int i = 0; i < CharsInDocument.Length; i++)
            {
                Console.ForegroundColor = colors[i];
                Char letter = CharsFactory.GetChar(CharsInDocument[i]);
                letter.DisplayText();
            }
 
            Console.WriteLine("\nNumber of repeating characters: " + CharsFactory.NumberRepeatingChars);
 
            Console.ReadKey();
        }
    }
}

On the client’s side, we set the color, i.e. define the color outside the object’s state, to save memory and efficiency. And we create a factory object to which in the loop we are putting in sequence the characters of the chain, which we defined at the beginning and we display this chain.

The result is this:

flyweightfirst.png

Repeated characters are E, T, so we have not created their objects for the second time. In a large application, this would save a lot of memory and performance.

Cache prototype

In this example, we’ll create a caching prototype that works in browsers, on the examples of images, we’ll load the whole thing the first time, but with the next actions we’ll load the pictures using their saved parameters. I put in a picture that I found on the web to make it easier to understand.

Flyweight_example1.png

And now the code:

namespace Cache
{
    abstract class Images
    {
        public string NameImage;
        public string TypeImage;
 
        public Images(string NameImage, string TypeImage)
        {
            this.NameImage = NameImage;
            this.TypeImage = TypeImage;
        }
 
        public void DisplayTextImage()
        {
            Console.WriteLine("The picture has name: " + NameImage + " about format " + TypeImage);
        }
    }
 
    class Imagepng : Images
    {
        public Imagepng(string NameImage) : base(NameImage, ".png") { }
    }
 
    class Imagejpeg : Images
    {
        public Imagejpeg(string NameImage) : base(NameImage, ".jpeg") { }
    }
 
    class Imagejpg : Images
    {
        public Imagejpg(string NameImage) : base(NameImage, ".jpg") { }
    }
 
    class ImagesFactory
    {
        public readonly Dictionary<string, Images> imagesList = new Dictionary<string, Images>();
 
        Images images = null;
 
        Regex regex1 = new Regex(@".[^.]*$");
        Regex regex2 = new Regex(@".*(?=\.)");
 
        Match result1;
        Match result2;
 
        public int NumberRepeatingImages;
 
        public Images SaveImage(string GiveImages)
        {
            result1 = regex1.Match(GiveImages);
            result2 = regex2.Match(GiveImages);
 
            if (imagesList.Keys.Contains(GiveImages))
            {
                NumberRepeatingImages++;
                images = imagesList[GiveImages];
            }
            else
            {
                switch (result1.ToString())
                {
                    case ".png":
                        images = new Imagepng(result2.ToString());
                        break;
                    case ".jpg":
                        images = new Imagejpg(result2.ToString());
                        break;
                    case ".jpeg":
                        images = new Imagejpeg(result2.ToString());
                        break;
                }
                imagesList.Add(GiveImages, images);
            }
 
            return images;
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("View website devman.pl");
 
            Console.WriteLine("Save the data to the cache memory");
 
            string image1 = "image1.jpg";
            string image2 = "image2.png";
            string image3 = "image3.png";
            string image4 = "image4.jpeg";
            string image5 = "image2.png";
            string image6 = "image3.jpg";
 
            string[] images = new string[] { image1, image2, image3, image4, image5, image6 };
 
            ImagesFactory factoryimages = new ImagesFactory();
 
            for (int i = 0; i < images.Length; i++)
            {
                Images letter = factoryimages.SaveImage(images[i]);
                letter.DisplayTextImage();
            }
 
            Console.WriteLine("Close the devman.pl website");
 
            Console.WriteLine("Display the devman.pl page again");
 
            Console.WriteLine("There are such pictures on the site");
 
            foreach (Images imagesTypes in factoryimages.imagesList.Values)
            {
                Console.WriteLine(imagesTypes.NameImage + " type of image format " + imagesTypes.TypeImage);
            }
 
            Console.WriteLine("The number of pictures with the same names and endings: " + factoryimages.NumberRepeatingImages);
 
            Console.ReadKey();
        }
    }
}

This is what the cache looks like in our action, we save the image names, their formats, image names with formats can sometimes be repeated, the code looks similar to the previous example.

For the first display of the page, we save the names of the pictures into the dictionary and their objects and using the foreach loops we display them in the client, after the second display of the page we already have the names in the dictionary, just we have to display them. In the Flyweight factory, we separate the name of the image from its format by means of a regex. The rest looks similar to the previous example.

First, we display all the pictures, then we display the pictures that were saved in the dictionary, so you can say in the cache, and we display the number of images that have the same names and formats. In our example there is one image that repeats twice.

Result:

flyweightsecond.png

A small update about multithreading in the Flyweight

Attention, a small update! I published this entry yesterday, but I forgot to mention also an important thing in the Flyweight, i.e. multithreading, as in the Flyweight we can have some clients who can use the same object, i.e. the Flyweight class must be implemented in a safe way or be type objects immutable (meaning a field with the readonly keyword), that is, unchangeable. And the Flyweight classes are singletons, in the singleton lesson on the blog I wrote that singleton classes should even have to be protected from many threads, because such classes can have only one instance.

But you need to have an knowledge about threads, tasks, multithreading about all this I wrote in the last two articles about C #, in articles about concurrency and asynchrony. After reading them everything will become clear. 🙂

But remember, the pattern is just like with the solid principles, rigid adherence to the rules of implementation in a larger project, to implement everything ideally is very rarely possible almost never, always adjust the pattern to your needs.

So do not act like this Mr below 🙂

FlyweightLindaEng.jpg

Flyweight vs Prototype

Flyweight is quite similar to the prototype pattern but slightly are different from each other, in other words:

  1. Flyweight is a structural pattern and the prototype is a creational design pattern.
  2. In the prototype pattern, we simply clone repeatedly one object, and in the Flyweight pattern we use the created objects again by sharing them, new objects are created only when there is no such or similar object in the application.
  3. The prototype is for creating new instances or cloning them, Flyweight is for creating and sharing them.

Summary

The end🙂

That’s all about Flyweight 🙂.

Link to github with the whole code from this article: https://github.com/Slaw145/FlyweightTutorial

This content also you can find on my blog http://devman.pl/programtech/design-patterns-flyweight/

If you recognise it as useful, share it with others so that others can also use it.

Leave upvote and follow and wait for next articles :) .

And this was the last article about structural patterns, the next lesson will be about the Object pool pattern. Similar to the Flyweight, but it is a construction pattern, but I preferred to describe it now because both patterns have similar goals. And in the next article after the Object pool pattern will be about the Interpreter pattern. So some plans have changed 🙂

And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place 🙂

– site on fb: Devman.pl-Sławomir Kowalski

– group on fb: DevmanCommunity

Ask, comment underneath at the end of the post, share it, rate it, whatever you want🙂.