Flyweight Pattern | Structural Design Pattern

Flyweight Pattern

The Flyweight Pattern is a structural design pattern that aims to minimize memory usage or computational expenses by sharing as much as possible with related objects. It is particularly useful when you need to create a large number of similar objects, and many of these objects have the same intrinsic (invariant) state.


Here are key concepts and characteristics of the Flyweight Pattern:


1. Intrinsic and Extrinsic State:

Intrinsic State: Represents shared information that can be stored in the flyweight and is independent of the context in which the flyweight is used.

Extrinsic State: Represents information that is unique to each instance and cannot be shared. This state is passed to the flyweight when needed.


2. Flyweight Interface:

   - Defines the interface through which flyweights are managed and can be used interchangeably.


3. Concrete Flyweights:

   - Implement the flyweight interface and represent the shared, reusable objects.


4. Flyweight Factory:

   - Manages the creation and retrieval of flyweights, ensuring that shared flyweights are reused rather than creating new instances.


5. Client:

   - Uses flyweights by requesting them from the flyweight factory and providing any necessary extrinsic state.


Here's a simple example in C# to illustrate the Flyweight Pattern. Let's consider a text editor where each character can be a flyweight:


_________

using System;
using System.Collections.Generic;

// Flyweight interface
public interface ICharacter
{
    void Display(int fontSize);
}

// Concrete Flyweight representing a character
public class ConcreteCharacter : ICharacter
{
    private char character;

    public ConcreteCharacter(char character)
    {
        this.character = character;
    }

    public void Display(int fontSize)
    {
        Console.WriteLine($"Character: {character}, Font Size: {fontSize}");
    }
}

// Flyweight Factory
public class CharacterFactory
{
    private Dictionary<char, ICharacter> characterPool = new Dictionary<char, ICharacter>();

    public ICharacter GetCharacter(char key)
    {
        if (!characterPool.ContainsKey(key))
        {
            characterPool[key] = new ConcreteCharacter(key);
        }
        return characterPool[key];
    }
}

class Program
{
    static void Main()
    {
        // Client code uses the flyweight factory to get and display characters
        CharacterFactory characterFactory = new CharacterFactory();

        ICharacter charA = characterFactory.GetCharacter('A');
        ICharacter charB = characterFactory.GetCharacter('B');
        ICharacter charA2 = characterFactory.GetCharacter('A'); // Reuses existing flyweight

        charA.Display(12);
        charB.Display(16);
        charA2.Display(14);
    }
}

__________

In this example:


- ICharacter is the flyweight interface.

- ConcreteCharacter is a concrete flyweight representing a character.

- CharacterFactory manages the creation and retrieval of flyweights.


When you run this program, it will output:

Character: A, Font Size: 12

Character: B, Font Size: 16

Character: A, Font Size: 14


This demonstrates how the Flyweight Pattern allows sharing of common flyweights (characters in this case) and how instances with the same intrinsic state are reused to save memory.


Intrinsic State: The intrinsic state represents the shared information that can be stored in the flyweight and is independent of the context in which the flyweight is used.

In this example, the intrinsic state is the character attribute in the ConcreteCharacter class. This is the state that is shared among multiple instances.

Extrinsic State: The extrinsic state represents information that is unique to each instance and cannot be shared. It is passed to the flyweight when needed.

In this example, the extrinsic state is the fontSize parameter passed to the Display method. Each time the Display method is called, the font size is unique to that particular invocation.



The Flyweight Pattern is indeed designed to minimize memory usage by sharing as much as possible among objects. While it keeps shared objects in memory, the key idea is to avoid creating a new instance for each occurrence of an object with the same intrinsic state. Instead, it shares instances, reducing the overall memory footprint.

The CharacterFactory in the example is responsible for managing and providing flyweights. When a client requests a character, the factory checks if an instance with the same intrinsic state (the character itself) already exists. If it does, the existing instance is returned; otherwise, a new one is created.


Benefits of the Flyweight Pattern:

Memory Efficiency: By sharing common state among multiple objects, the pattern minimizes memory usage, especially when dealing with a large number of similar objects.

Performance Improvement: The pattern can improve performance by reducing the overhead associated with creating and managing a large number of instances.

Reuse of Objects: Instances with the same intrinsic state are reused, promoting object reusability.

Simplified Client Code: Clients can work with flyweights through a common interface, making the code simpler and more modular.

Intrinsic and Extrinsic Separation: The pattern encourages a clear separation between intrinsic and extrinsic state, making the design more flexible and maintainable.


Flyweight Pattern is beneficial when you have a large number of objects with shared state, and the overhead of creating and managing individual instances is significant. It helps strike a balance between memory efficiency and object reusability.



Example 2

Let's consider a scenario where we want to represent and manage a large number of unique trees in a forest. The trees share certain intrinsic characteristics (e.g., tree type, color) but have unique extrinsic characteristics (e.g., position in the forest). The Flyweight Pattern can help optimize memory usage in this context.

_______

using System;
using System.Collections.Generic;

// Flyweight interface
public interface ITree
{
    void Display(int x, int y);
}

// Concrete Flyweight representing a shared tree type
public class TreeType : ITree
{
    private string name;
    private string color;

    public TreeType(string name, string color)
    {
        this.name = name;
        this.color = color;
    }

    public void Display(int x, int y)
    {
        Console.WriteLine($"Tree Type: {name}, Color: {color}, Position: ({x}, {y})");
    }
}

// Flyweight Factory
public class TreeFactory
{
    private Dictionary<string, TreeType> treeTypes = new Dictionary<string, TreeType>();

    public TreeType GetTreeType(string name, string color)
    {
        string key = $"{name}_{color}";

        if (!treeTypes.ContainsKey(key))
        {
            treeTypes[key] = new TreeType(name, color);
        }

        return treeTypes[key];
    }
}

// Client representing the forest
public class Forest
{
    private List<(ITree, int, int)> trees = new List<(ITree, int, int)>();
    private TreeFactory treeFactory = new TreeFactory();

    public void PlantTree(string treeType, string color, int x, int y)
    {
        ITree treeTypeInstance = treeFactory.GetTreeType(treeType, color);
        trees.Add((treeTypeInstance, x, y));
    }

    public void DisplayForest()
    {
        foreach (var treeTuple in trees)
        {
            treeTuple.Item1.Display(treeTuple.Item2, treeTuple.Item3);
        }
    }
}

class Program
{
    static void Main()
    {
        // Client code uses the Flyweight Pattern to represent a forest of trees
        Forest forest = new Forest();

        forest.PlantTree("Oak", "Green", 10, 20);
        forest.PlantTree("Pine", "Brown", 15, 25);
        forest.PlantTree("Oak", "Green", 30, 40);

        forest.DisplayForest();
    }
}
______

In this example:

ITree is the flyweight interface.TreeType is a concrete flyweight representing a shared tree type.

TreeFactory manages the creation and retrieval of flyweights (tree types).

Forest is the client representing a collection of trees in a forest.

When you run this program, it will output:

Tree Type: Oak, Color: Green, Position: (10, 20)

Tree Type: Pine, Color: Brown, Position: (15, 25)

Tree Type: Oak, Color: Green, Position: (30, 40)

This example illustrates how the Flyweight Pattern can be applied to efficiently represent and manage a large number of similar objects with shared intrinsic state while allowing for unique extrinsic state for each instance.



Exercise: Implement a Music Playlist

Consider a music playlist where songs have intrinsic information like the title, artist, and duration, but extrinsic information like the position in the playlist is unique to each song instance. Use the Flyweight Pattern to efficiently manage and display the playlist.



Solution:

- Create a ISong interface with a method Play(int position) to display information about a song at a given position.

- Implement a ConcreteSong class that implements ISong. This class should represent a song with intrinsic information such as title, artist, and duration.

- Create a SongFactory class that manages the creation and retrieval of flyweights (songs). Ensure that shared songs are reused.

- Develop a Playlist class that represents a collection of songs in a playlist. It should allow adding songs to the playlist with their positions.

- Write client code to demonstrate the use of the Flyweight Pattern for managing and displaying a music playlist.

__________
using System;
using System.Collections.Generic;

// Flyweight interface
public interface ISong
{
    void Play(int position);
}

// Concrete Flyweight representing a song
public class ConcreteSong : ISong
{
    private string title;
    private string artist;
    private TimeSpan duration;

    public ConcreteSong(string title, string artist, TimeSpan duration)
    {
        this.title = title;
        this.artist = artist;
        this.duration = duration;
    }

    public void Play(int position)
    {
        Console.WriteLine($"Playing song at position {position}: '{title}' by {artist} ({duration:mm\\:ss})");
    }
}

// Flyweight Factory
public class SongFactory
{
    private Dictionary<string, ISong> songs = new Dictionary<string, ISong>();

    public ISong GetSong(string title, string artist, TimeSpan duration)
    {
        string key = $"{title}_{artist}_{duration}";

        if (!songs.ContainsKey(key))
        {
            songs[key] = new ConcreteSong(title, artist, duration);
        }

        return songs[key];
    }
}

// Client representing a music playlist
public class Playlist
{
    private List<(ISong, int)> songsInPlaylist = new List<(ISong, int)>();
    private SongFactory songFactory = new SongFactory();

    public void AddSongToPlaylist(string title, string artist, TimeSpan duration, int position)
    {
        ISong songInstance = songFactory.GetSong(title, artist, duration);
        songsInPlaylist.Add((songInstance, position));
    }

    public void PlayPlaylist()
    {
        foreach (var songTuple in songsInPlaylist)
        {
            songTuple.Item1.Play(songTuple.Item2);
        }
    }
}

class Program
{
    static void Main()
    {
        // Client code uses the Flyweight Pattern to represent a music playlist
        Playlist musicPlaylist = new Playlist();

        musicPlaylist.AddSongToPlaylist("Song1", "Artist1", TimeSpan.FromMinutes(3), 1);
        musicPlaylist.AddSongToPlaylist("Song2", "Artist2", TimeSpan.FromMinutes(4), 2);
        musicPlaylist.AddSongToPlaylist("Song1", "Artist1", TimeSpan.FromMinutes(3), 3); // Reuses existing flyweight

        musicPlaylist.PlayPlaylist();
    }
}
___________

In this solution:

ISong is the flyweight interface.

ConcreteSong is a concrete flyweight representing a song.

SongFactory manages the creation and retrieval of flyweights (songs).

Playlist is the client representing a collection of songs in a playlist.

When you run this program, it will output:

Playing song at position 1: 'Song1' by Artist1 (03:00)

Playing song at position 2: 'Song2' by Artist2 (04:00)

Playing song at position 3: 'Song1' by Artist1 (03:00)


This exercise demonstrates how the Flyweight Pattern can be applied to efficiently represent and manage a large number of similar objects (songs) with shared intrinsic state while allowing for unique extrinsic state (position) for each instance.

Comments