Real-Time Examples of Flyweight Design Patterns in C#

Real-Time Examples of the Flyweight Design Pattern in C#

In this article, I will discuss Multiple Real-Time Examples of the Flyweight Design Pattern in C#. Please read our previous article discussing the basics of Flyweight Design Patterns in C# with Examples. At the end of this article, you will understand the following Real-time Examples using Flyweight Design Pattern in C#.

  1. Forest Simulation
  2. Coffee Shop that Offers Various Flavors of Coffee
  3. 2D Computer-Aided Design (CAD) System
  4. Vehicle License Plate Management System
  5. A Text Editor

Note: The Flyweight design pattern is about using memory efficiently. It is used when we need to instantiate many objects, but many share most of their state. Instead of creating a unique object for each one, we can share parts of their state and reduce memory consumption.

Real-Time Example of Flyweight Design Pattern in C#: Forest Simulation

Imagine we have a forest with thousands of trees, and each tree has attributes like species, age, health status, and so on. However, a tree’s graphics representation (color, texture) mainly depends on its species and not the individual tree.

In this scenario, the tree’s graphical representation (texture, color, etc.) can be the shared state (intrinsic), while the tree’s position, age, and health can be unique for each tree (extrinsic). Let us see how we can implement the above example using the Flyweight Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace FlyweightDesignPattern
{
    // The Flyweight class
    class TreeType
    {
        public string Name { get; }
        public string Color { get; }
        public string Texture { get; }

        public TreeType(string name, string color, string texture)
        {
            Name = name;
            Color = color;
            Texture = texture;
        }

        public void Draw(int x, int y)
        {
            Console.WriteLine($"Drawn a {Name} tree with {Color} color and {Texture} texture at ({x}, {y})");
        }
    }

    // The Flyweight Factory
    class TreeFactory
    {
        private static readonly Dictionary<string, TreeType> _treeTypes = new Dictionary<string, TreeType>();

        public static TreeType GetTreeType(string name, string color, string texture)
        {
            var key = $"{name}_{color}_{texture}";
            if (!_treeTypes.ContainsKey(key))
            {
                var type = new TreeType(name, color, texture);
                _treeTypes.Add(key, type);
            }

            return _treeTypes[key];
        }
    }

    // The context class that uses the Flyweight
    class Tree
    {
        private readonly TreeType _treeType;
        private readonly int _x;
        private readonly int _y;

        public Tree(int x, int y, TreeType treeType)
        {
            _x = x;
            _y = y;
            _treeType = treeType;
        }

        public void Draw()
        {
            _treeType.Draw(_x, _y);
        }
    }
    
    //Simulation (Client Usage)
    //Testing the Flyweight Design Pattern 
    public class Program
    {
        public static void Main()
        {
            var forest = new List<Tree>();

            // Adding trees to forest
            forest.Add(new Tree(1, 2, TreeFactory.GetTreeType("Pine", "Green", "PineTexture")));
            forest.Add(new Tree(10, 20, TreeFactory.GetTreeType("Pine", "Green", "PineTexture")));
            forest.Add(new Tree(5, 7, TreeFactory.GetTreeType("Oak", "Brown", "OakTexture")));

            foreach (var tree in forest)
            {
                tree.Draw();
            }

            Console.ReadKey();
        }
    }
}

Here, TreeType is our Flyweight class. The TreeFactory ensures we’re reusing existing TreeType objects and not unnecessarily creating new ones. The Tree class represents individual trees in the forest, and each has its own position but shares a TreeType. The client code in the Program demonstrates that even though we’ve added two Pine trees, the TreeType object for “Pine” is created only once. When you run the above code, you will get the following output.

Real-Time Example of Flyweight Design Pattern in C#: Coffee Shop that Offers Various Flavors of Coffee

Let’s see a real-time example of a Coffee Shop that offers various flavors of coffee. A large coffee shop might serve thousands of cups daily, many of which are of the same flavor. We can use the Flyweight pattern to manage the flavors efficiently. In this example:

  • Shared State (Intrinsic state): The type of coffee, its ingredients, and the preparation method.
  • Unique State (Extrinsic state): The table number for the order or any specific customizations a customer requests.

Let us see how we can implement the above example using the Flyweight Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace FlyweightDesignPattern
{
    // Flyweight
    public class Coffee
    {
        public string Flavor { get; private set; }
        public string Ingredients { get; private set; }
        public string Preparation { get; private set; }

        public Coffee(string flavor, string ingredients, string preparation)
        {
            Flavor = flavor;
            Ingredients = ingredients;
            Preparation = preparation;
        }

        public void ServeCoffee(int tableNumber, string customizations = "")
        {
            Console.WriteLine($"Serving {Flavor} coffee (made with {Ingredients} and {Preparation}) to table {tableNumber}. {customizations}");
        }
    }

    // Flyweight Factory
    public class CoffeeFactory
    {
        private readonly Dictionary<string, Coffee> _coffees = new Dictionary<string, Coffee>();

        public Coffee GetCoffee(string flavor)
        {
            if (!_coffees.ContainsKey(flavor))
            {
                // For simplicity, let's assume every coffee just has water as its ingredient and is brewed.
                var coffee = new Coffee(flavor, "water", "brewed");
                _coffees.Add(flavor, coffee);
            }

            return _coffees[flavor];
        }
    }

    // Client
    public class CoffeeShop
    {
        private readonly CoffeeFactory _coffeeFactory = new CoffeeFactory();
        private readonly List<Tuple<Coffee, int, string>> _orders = new List<Tuple<Coffee, int, string>>();

        public void TakeOrder(string flavor, int tableNumber, string customizations = "")
        {
            var coffee = _coffeeFactory.GetCoffee(flavor);
            _orders.Add(Tuple.Create(coffee, tableNumber, customizations));
        }

        public void ServeOrders()
        {
            foreach (var order in _orders)
            {
                order.Item1.ServeCoffee(order.Item2, order.Item3);
            }
            _orders.Clear();  // Once served, clear the orders
        }
    }
    
    // Client Usage
    //Testing the Flyweight Design Pattern 
    public class Program
    {
        public static void Main()
        {
            var shop = new CoffeeShop();

            shop.TakeOrder("Cappuccino", 1);
            shop.TakeOrder("Espresso", 2, "With extra sugar");
            shop.TakeOrder("Cappuccino", 3);
            shop.TakeOrder("Latte", 4);

            shop.ServeOrders();

            Console.ReadKey();
        }
    }
}

In this design, the Coffee class represents our Flyweight. The shared state (coffee flavor, ingredients, and preparation) is stored in this class. The CoffeeFactory ensures that we create each coffee flavor only once. The CoffeeShop class simulates the process of taking and serving orders. When you run the above code, you will get the following output.

Real-Time Example of Flyweight Design Pattern in C#: 2D Computer-Aided Design (CAD) System

Let’s discuss another real-time example to understand the Flyweight design pattern: a 2D Computer-Aided Design (CAD) System where users can draw different shapes, like circles, rectangles, etc. Each shape might have its own properties like color, pattern, and thickness. The flyweight pattern can be applied for efficiency, especially when there are many similar shapes in a drawing.

In this scenario:

  • Shared State (Intrinsic state): The shape’s color, pattern, and thickness.
  • Unique State (Extrinsic state): The shape’s position (coordinates) and size.

Considering the vast number of shapes a design might have, storing separate color information for each shape is inefficient if they share the same color. We can use the Flyweight pattern to optimize this. Let us see how we can implement the above example using the Flyweight Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace FlyweightDesignPattern
{
    // Flyweight: ShapeStyle holds the shared properties
    public class ShapeStyle
    {
        public string Color { get; }
        public string Pattern { get; }
        public int Thickness { get; }

        public ShapeStyle(string color, string pattern, int thickness)
        {
            Color = color;
            Pattern = pattern;
            Thickness = thickness;
        }

        public void DisplayProperties()
        {
            Console.WriteLine($"Style [Color: {Color}, Pattern: {Pattern}, Thickness: {Thickness}]");
        }
    }

    // Flyweight Factory
    public class StyleFactory
    {
        private readonly Dictionary<string, ShapeStyle> _styles = new Dictionary<string, ShapeStyle>();

        public ShapeStyle GetStyle(string color, string pattern, int thickness)
        {
            var key = $"{color}_{pattern}_{thickness}";

            if (!_styles.ContainsKey(key))
            {
                _styles[key] = new ShapeStyle(color, pattern, thickness);
            }

            return _styles[key];
        }
    }

    // The ConcreteFlyweight class with external states.
    public class Circle
    {
        private readonly ShapeStyle _style;
        public int X { get; }
        public int Y { get; }
        public int Radius { get; }

        public Circle(int x, int y, int radius, ShapeStyle style)
        {
            X = x;
            Y = y;
            Radius = radius;
            _style = style;
        }

        public void Draw()
        {
            Console.WriteLine($"Drawing Circle at ({X}, {Y}) with Radius {Radius}");
            _style.DisplayProperties();
        }
    }

    // Client
    class CADSystem
    {
        private readonly List<Circle> _circles = new List<Circle>();
        private readonly StyleFactory _styleFactory = new StyleFactory();

        public void AddCircle(int x, int y, int radius, string color, string pattern, int thickness)
        {
            var style = _styleFactory.GetStyle(color, pattern, thickness);
            _circles.Add(new Circle(x, y, radius, style));
        }

        public void DrawAllShapes()
        {
            foreach (var circle in _circles)
            {
                circle.Draw();
            }
        }
    }
    
    // Client Usage
    //Testing the Flyweight Design Pattern 
    public class Program
    {
        public static void Main()
        {
            var cadSystem = new CADSystem();

            cadSystem.AddCircle(5, 5, 10, "Red", "Solid", 2);
            cadSystem.AddCircle(15, 15, 20, "Blue", "Dotted", 3);
            cadSystem.AddCircle(25, 25, 30, "Red", "Solid", 2);

            cadSystem.DrawAllShapes();

            Console.ReadKey();
        }
    }
}

The ShapeStyle class represents our Flyweight, holding shared properties like color, pattern, and thickness. The StyleFactory ensures we reuse styles wherever possible. The Circle class is a specific shape that uses this style. I’ve only demonstrated the Circle shape for brevity, but this approach can be extended to rectangles, lines, etc. The CADSystem class handles the drawing of shapes. When you run the above code, you will get the following output.

Real-Time Example of Flyweight Design Pattern in C#: Vehicle License Plate Management System

Let’s consider a real-time scenario of a Vehicle License Plate Management System. Each vehicle in a country or region might have a unique license plate, but the format or template of the license plate might be the same across many vehicles. The format can have patterns like “XXX-0000”, “XX-00-XX”, etc., where X represents letters and 0 represents numbers.

In a Vehicle License Plate Management System, the Flyweight pattern can be applied to manage license plate numbers efficiently. In this scenario:

  • Shared State (Intrinsic state): The prefix representing the state or region.
  • Unique State (Extrinsic state): The unique number for each vehicle.

Given the large number of vehicles, storing shared license plate formats as flyweights is efficient. The unique plate number for each vehicle will be the extrinsic state. Let us see how we can implement the above example using the Flyweight Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace FlyweightDesignPattern
{
    // Flyweight: LicensePlatePrefix holds the shared properties
    public class LicensePlatePrefix
    {
        public string StateOrRegion { get; }

        public LicensePlatePrefix(string stateOrRegion)
        {
            StateOrRegion = stateOrRegion;
        }

        public void DisplayPrefix()
        {
            Console.WriteLine($"Prefix: {StateOrRegion}");
        }
    }

    // Flyweight Factory
    public class PrefixFactory
    {
        private readonly Dictionary<string, LicensePlatePrefix> _prefixes = new Dictionary<string, LicensePlatePrefix>();

        public LicensePlatePrefix GetPrefix(string stateOrRegion)
        {
            if (!_prefixes.ContainsKey(stateOrRegion))
            {
                _prefixes[stateOrRegion] = new LicensePlatePrefix(stateOrRegion);
            }

            return _prefixes[stateOrRegion];
        }
    }

    // The ConcreteFlyweight class with external states.
    public class LicensePlate
    {
        private readonly LicensePlatePrefix _prefix;
        public int UniqueNumber { get; }

        public LicensePlate(int uniqueNumber, LicensePlatePrefix prefix)
        {
            UniqueNumber = uniqueNumber;
            _prefix = prefix;
        }

        public void DisplayPlate()
        {
            _prefix.DisplayPrefix();
            Console.WriteLine($"Unique Number: {UniqueNumber}");
        }
    }

    // Client
    class LicensePlateSystem
    {
        private readonly List<LicensePlate> _plates = new List<LicensePlate>();
        private readonly PrefixFactory _prefixFactory = new PrefixFactory();

        public void RegisterVehicle(string stateOrRegion, int uniqueNumber)
        {
            var prefix = _prefixFactory.GetPrefix(stateOrRegion);
            _plates.Add(new LicensePlate(uniqueNumber, prefix));
        }

        public void DisplayAllPlates()
        {
            foreach (var plate in _plates)
            {
                plate.DisplayPlate();
                Console.WriteLine("-----");
            }
        }
    }
    
    // Client Usage
    //Testing the Flyweight Design Pattern 
    public class Program
    {
        public static void Main()
        {
            var plateSystem = new LicensePlateSystem();

            plateSystem.RegisterVehicle("NY", 12345);
            plateSystem.RegisterVehicle("CA", 67890);
            plateSystem.RegisterVehicle("NY", 54321);

            plateSystem.DisplayAllPlates();

            Console.ReadKey();
        }
    }
}

In this example, the LicensePlatePrefix class is the Flyweight holding the shared prefix for each state or region. The PrefixFactory manages and reuses these shared prefixes. The LicensePlate class contains the unique number for each vehicle and utilizes the shared prefix. The LicensePlateSystem class manages the system, registration, and license plate display. When you run the above code, you will get the following output.

Real-Time Example of Flyweight Design Pattern in C#: a Text Editor

Let’s consider a real-time scenario of a Text Editor. When a text editor loads a document, it might contain thousands of characters. Each character might have formatting attributes, like bold, italic, underline, font type, font size, and color. Given two characters with the same formatting, storing formatting information separately for each character is inefficient. The Flyweight pattern can help in such situations.

In this scenario:

  • Shared State (Intrinsic state): Font, size, and style (e.g., bold, italic), which are common character properties.
  • Unique State (Extrinsic state): The character and position in the document.

Let us see how we can implement the above example using the Flyweight Design Pattern in C#:

using System;
using System.Collections.Generic;

namespace FlyweightDesignPattern
{
    // Flyweight: CharacterStyle holds the shared properties
    public class CharacterStyle
    {
        public string Font { get; }
        public int Size { get; }
        public bool IsBold { get; }
        public bool IsItalic { get; }

        public CharacterStyle(string font, int size, bool isBold, bool isItalic)
        {
            Font = font;
            Size = size;
            IsBold = isBold;
            IsItalic = isItalic;
        }

        public void DisplayStyle()
        {
            Console.WriteLine($"Font: {Font}, Size: {Size}, Bold: {IsBold}, Italic: {IsItalic}");
        }
    }

    // Flyweight Factory
    public class StyleFactory
    {
        private readonly Dictionary<string, CharacterStyle> _styles = new Dictionary<string, CharacterStyle>();

        public CharacterStyle GetStyle(string font, int size, bool isBold, bool isItalic)
        {
            var key = $"{font}_{size}_{isBold}_{isItalic}";

            if (!_styles.ContainsKey(key))
            {
                _styles[key] = new CharacterStyle(font, size, isBold, isItalic);
            }

            return _styles[key];
        }
    }

    // The ConcreteFlyweight class with external states.
    public class Character
    {
        private readonly CharacterStyle _style;
        public char Symbol { get; }
        public int Position { get; }

        public Character(char symbol, int position, CharacterStyle style)
        {
            Symbol = symbol;
            Position = position;
            _style = style;
        }

        public void Display()
        {
            Console.Write(Symbol);
            _style.DisplayStyle();
        }
    }

    // Client
    class TextEditor
    {
        private readonly List<Character> _document = new List<Character>();
        private readonly StyleFactory _styleFactory = new StyleFactory();

        public void InsertCharacter(char symbol, int position, string font, int size, bool isBold, bool isItalic)
        {
            var style = _styleFactory.GetStyle(font, size, isBold, isItalic);
            _document.Add(new Character(symbol, position, style));
        }

        public void DisplayDocument()
        {
            foreach (var character in _document)
            {
                character.Display();
                Console.WriteLine("-----");
            }
        }
    }
    
    // Client Usage
    //Testing the Flyweight Design Pattern 
    public class Program
    {
        public static void Main()
        {
            var editor = new TextEditor();

            editor.InsertCharacter('A', 0, "Arial", 12, true, false);
            editor.InsertCharacter('B', 1, "Arial", 12, true, false);
            editor.InsertCharacter('C', 2, "Times New Roman", 14, false, true);

            editor.DisplayDocument();

            Console.ReadKey();
        }
    }
}

The CharacterStyle class is our Flyweight in this design, holding shared properties like font, size, and styles. The StyleFactory ensures we reuse character styles wherever possible. The Character class represents individual characters in the document. The TextEditor class simulates the operations of the editor. When you run the above code, you will get the following output.

Advantages and Disadvantages of Flyweight Design Pattern in C#

The Flyweight design pattern provides a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. It promotes the sharing of common data across objects to save space. Here are the advantages and disadvantages of the Flyweight pattern:

Advantages of Flyweight Design Pattern in C#:

Memory Efficiency: One of the primary benefits of the Flyweight pattern is a reduction in memory usage. By sharing common data among multiple objects, you save memory that would have been consumed if each object stored its own copy of the data.

  • Faster Access: Having fewer instances (due to shared objects) could mean faster access, which is particularly beneficial when dealing with many objects.
  • Centralized State Management: Changes to the shared object will be reflected across all the objects using it. This means the state can be managed and updated in one place.
  • Scalability: The pattern lends itself well to scalable systems. Suppose your system needs to handle a large number of objects that have a lot of overlapping states. In that case, the Flyweight pattern can significantly affect performance and memory footprint.
Disadvantages of Flyweight Design Pattern in C#:
  • Complexity: Implementing the Flyweight pattern can increase complexity, especially when distinguishing between intrinsic (shared) and extrinsic (unshared) states. This can make the system harder to understand and maintain.
  • Overhead: If applied inappropriately, the overhead of managing flyweight objects can outweigh the benefits. It’s essential to assess whether the pattern is suitable for the specific use case you’re considering.
  • Shared State Side Effects: Since the state is shared among objects, any changes to the shared object can affect all objects that reference it. This behavior can be counter-intuitive and lead to unwanted side effects if not managed carefully.
  • State Management: Keeping track of the shared and unique data separately might become cumbersome, especially if the shared data needs to be modified frequently.

The Flyweight pattern’s benefits and challenges are similar in C#, as in many other object-oriented languages. However, C# provides several mechanisms (like dictionaries, hash sets, etc.) that make implementing the Flyweight pattern relatively straightforward. But as with all patterns, it’s crucial to understand the trade-offs and ensure it’s the right fit for the problem you’re trying to solve.

In the next article, I will discuss the Proxy Design Pattern in C# with Examples. Here, in this article, I try to explain Real-Time Examples of Flyweight Design Patterns in C#. I hope you enjoy this Flyweight Design Pattern in Real-Time Examples using the C# article.

Leave a Reply

Your email address will not be published. Required fields are marked *