Design Patterns - Flyweight

20 May 2022 at 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we had discussed the facade design pattern, which falls under the category of structural design patterns. Today, we will explore another design pattern from the same category called Flyweight Design Pattern.

Table of contents

  1. Introduction to Flyweight design pattern,
  2. Implementation of Flyweight design pattern
  3. Conclusion

Introduction to Flyweight design pattern

A flyweight design pattern is primarily used when the performance of a system needs to be increased or optimized. This design pattern achieves the desired performance by storing the object creation in some other object during the first call, and reusing it when it is requested for the second time.

The functionality is more or less similar to a cache where we store the data and reuse it as long as it is valid. There is also a slight similarity between the Singleton pattern (creational design pattern) and this one - both are created to achieve the purpose of having only one object of the particular class throughout the code.

The difference between the two is that in Singleton, the second instance of the object is never allowed to be created. Instead, the same object remains throughout until the code is live. In flyweight, there is a relaxation that if the object is not present then it can be recreated and stored. Singleton pattern mainly deals with how the object is created, while flyweight, mainly deals with how it is used throughout the code (than how it is created).

When a change is made in one instance of an object in a flyweight pattern, then it will have an impact (value gets changed) on all the other objects of the same type which are pointing to the same reference.

Implementation of Flyweight design pattern

The flyweight design pattern mainly consists of:

  • Flyweight factory
  • Flyweight
  • Concrete flyweight
  • Unshared flyweigh

Let’s understand this with the help of a program.

using System;
using System.Collections;

namespace PARTECH_Flyweight
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var flyweightFactory = new FlyweightFactory();

            var concreteObject = flyweightFactory.GetFlyweight("A");

            concreteObject.Operation("Concrete Object retrieved");

            Console.ReadLine();
        }
    }

    public interface IFlyweight
    {
        void Operation(string data);
    }

    public class ConcreteFlyweight : IFlyweight
    {
        public void Operation(string data)
        {
            Console.WriteLine(data);
        }
    }

    public class CocnreteUnsharedFlyweight : IFlyweight
    {
        public void Operation(string data)
        {
            Console.WriteLine(data);
        }
    }


    public class FlyweightFactory
    {
        private Hashtable flyweights = new Hashtable();

        public IFlyweight GetFlyweight(string key)
        {
            if (flyweights.ContainsKey(key))
            {
                return flyweights[key] as IFlyweight;
            }
            else
            {
                IFlyweight newlyCreated = new ConcreteFlyweight();

                flyweights.Add(key, newlyCreated);

                return newlyCreated;
            }
        }
    }
}

Here,

IFlyweight is an interface that has the definition for the method that needs to be implemented. ConcreteFlyweight is a class that implements the IFlyweight interface.

ConcreteUnsharedFlyweight is a class that also implements the IFlyweight interface. However, they do not participate in the object reusing concept. Instead whenever there is a need, a new object is created for them.

FlyweightFactory is the brain of this pattern where the logic to reuse the object is written. Here, we have used Hashtable as the global property of the class to store the created objects. We can also use dictionaries, tuples, or any class of custom type to store the objects.

Similarly, here we are maintaining the created objects within the factory property. It is also possible to use the CacheManager available in .Net and store and retrieve the object from there. Depending on the use case, the required collection and cache technique can be chosen.

And in the main method, the flyweight factory object is first created. Now, if we use cache manager inside the flyweight factory, we can make the GetFlyweight method static and can access it without creating an object for the flyweight factory. Then, the key is passed to fetch the appropriate object.

Let’s implement the Flyweight design pattern for a gaming application for kids which offers basic information about animals and their sounds. In our example, 5 animals are listed on the screen, and when users select an animal, a corresponding action takes place. Now, there are chances that the same animal can be clicked multiple times in the application. So instead of creating the object of the particular animal on every click, we are going to implement the flyweight design pattern to achieve better performance.

using System;
using System.Collections;

namespace PARTECH_Flyweight
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var factory = new AnimalFactory();

            var cat = factory.GetAnimalInstances("Cat");

            cat.BasicDetails();
            cat.AnimalName = "Tom";

            var dog = factory.GetAnimalInstances("Dog");
            dog.BasicDetails();

            cat = factory.GetAnimalInstances("Cat");
            cat.BasicDetails();

            Console.ReadLine();
        }
    }
    public interface IAnimal
    {
        string AnimalName { get; set; }
        void BasicDetails();

        void TimesCalled(int count)
        {
            Console.WriteLine(count);
        }
    }

    public class Cat: IAnimal
    {
        public string _animalName;

        public void BasicDetails()
        {
            Console.WriteLine("Prints the basic info of Cat");
            Console.WriteLine($"Name - {AnimalName}");
        }

        public string AnimalName
        {
            get => _animalName;
            set => _animalName = value;
        }

    }

    public class Dog : IAnimal
    {
        public string _animalName;

        public void BasicDetails()
        {
            Console.WriteLine("Prints the basic info of Dog");
        }

        public string AnimalName
        {
            get => _animalName;
            set => _animalName = value;
        }

    }

    public class Horse : IAnimal
    {
        public string _animalName;

        public void BasicDetails()
        {
            Console.WriteLine("Prints the basic info of Horse");
        }

        public string AnimalName
        {
            get => _animalName;
            set => _animalName = value;
        }

    }

    public class AnimalFactory
    {
        private Hashtable animalInstances = new Hashtable();

        public IAnimal GetAnimalInstances(string key)
        {
            if (animalInstances.ContainsKey(key))
            {
                return animalInstances[key] as IAnimal;
            }
            else
            {
                IAnimal animal = null;

                switch(key)
                {
                    case "Cat":
                        animal = new Cat();
                        break;
                    case "Dog":
                        animal = new Dog();
                        break;
                    case "Horse":
                        animal = new Horse();
                        break;
                }

                animalInstances.Add(key, animal);

                return animal;
            }
        }
    }
}

In the above code, IAnimal is the IFlyweight interface. The classes Cat, Dog and Horse are the concrete flyweights and Animalfactory is the flyweight factory. Here are the next steps-

  • In the main method, an object for the animal factory is first created.
  • Then, with that, an instance of a ‘Cat’ is obtained.
  • Since this is the first time, an object is created and stored in the hashtable.
  • The same process happens in the case of ‘Dog’.
  • We will set the animal name for the ‘Cat’ object as Tom.
  • We will now we try to get the ‘Cat’ object again.
  • When we print the details, it prints the Cat object with its name.

Conclusion

And that’s what a Flyweight Structural design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.

Latest