Skip to content

Flyweight Design Pattern

Video Lecture

Section Video Links
Flyweight Overview Flyweight Overview Flyweight Overview Flyweight Overview 
Flyweight Use Case Flyweight Use Case Flyweight Use Case Flyweight Use Case 
String Justification String Justification String Justification String Justification 

Overview

Fly in the term Flyweight means light/not heavy.

Instead of creating thousands of objects that share common attributes, and result in a situation where a large amount of memory or other resources are used, you can modify your classes to share multiple instances simultaneously by using some kind of reference to the shared object instead.

The best example to describe this is a document containing many words and sentences and made up of many letters. Rather than storing a new object for each individual letter describing its font, position, color, padding and many other potential things. You can store just a lookup id of a character in a collection of some sort and then dynamically create the object with its proper formatting etc., only as you need to.

This approach saves a lot of memory at the expense of using some extra CPU instead to create the object at presentation time.

The Flyweight pattern, describes how you can share objects rather than creating thousands of almost repeated objects unnecessarily.

A Flyweight acts as an independent object in any number of contexts. A context can be a cell in a table, or a div on a html page. A context is using the Flyweight.

You can have many contexts, and when they ask for a Flyweight, they will get an object that may already be shared amongst other contexts, or already within it self somewhere else.

When describing flyweights, it is useful to describe it in terms of intrinsic and extrinsic attributes.

Intrinsic (in or including) are the attributes of a flyweight that are internal and unique from the other flyweights. E.g., a new flyweight for every letter of the alphabet. Each letter is intrinsic to the flyweight.

Extrinsic (outside or external) are the attributes that are used to present the flyweight in terms of the context where it will be used. E.g., many letters in a string can be right aligned with each other. The extrinsic property of each letter is the new positioning of its X and Y on a grid.

Terminology

  • Flyweight Interface: An interface where a flyweight receives its extrinsic attributes.
  • Concrete Flyweight: The flyweight object that stores the intrinsic attributes and implements the interface to apply extrinsic attributes.
  • Unshared Flyweights: Not all flyweights will be shared, the flyweight enables sharing, not enforcing it. It also possible that flyweights can share other flyweights but still not yet be used in any contexts anywhere.
  • Flyweight Factory: Creates and manages flyweights at runtime. It reuses flyweights in memory, or creates a new one in demand.
  • Client: The client application that uses and creates the Flyweight.

Flyweight UML Diagram

Flyweight Pattern UML Diagram

Source Code

...Refer to Book or Videos for extra content.

./flyweight/flyweight_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# pylint: disable=too-few-public-methods
"The Flyweight Concept"

class IFlyweight():
    "Nothing to implement"

class Flyweight(IFlyweight):
    "The Concrete Flyweight"

    def __init__(self, code: int) -> None:
        self.code = code

class FlyweightFactory():
    "Creating the FlyweightFactory as a singleton"

    _flyweights: dict[int, Flyweight]= {}

    def __new__(cls):
        return cls

    @classmethod
    def get_flyweight(cls, code: int) -> Flyweight:
        "A static method to get a flyweight based on a code"
        if not code in cls._flyweights:
            cls._flyweights[code] = Flyweight(code)
        return cls._flyweights[code]

    @classmethod
    def get_count(cls) -> int:
        "Return the number of flyweights in the cache"
        return len(cls._flyweights)

class Context():
    """
    An example context that holds references to the flyweights in a
    particular order and converts the code to an ascii letter
    """

    def __init__(self, codes: str) -> None:
        self.codes = list(codes)

    def output(self):
        "The context specific output that uses flyweights"
        ret = ""
        for code in self.codes:
            ret = ret + FlyweightFactory.get_flyweight(code).code
        return ret

# The Client
CONTEXT = Context("abracadabra")

# use flyweights in a context
print(CONTEXT.output())

print(f"abracadabra has {len('abracadabra')} letters")
print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights")

Output

1
2
3
4
python ./flyweight/flyweight_concept.py
abracadabra
abracadabra has 11 letters
FlyweightFactory has 5 flyweights

Example Use Case

...Refer to Book or Videos for extra content.

Example UML Diagram

Flyweight Pattern Use Case UML Diagram

Source Code

./flyweight/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"The Flyweight Use Case Example"

from table import Table
from flyweight_factory import FlyweightFactory

TABLE = Table(3, 3)

TABLE.rows[0].columns[0].data = "abra"
TABLE.rows[0].columns[1].data = "112233"
TABLE.rows[0].columns[2].data = "cadabra"
TABLE.rows[1].columns[0].data = "racadab"
TABLE.rows[1].columns[1].data = "12345"
TABLE.rows[1].columns[2].data = "332211"
TABLE.rows[2].columns[0].data = "cadabra"
TABLE.rows[2].columns[1].data = "445566"
TABLE.rows[2].columns[2].data = "aa 22 bb"

TABLE.rows[0].columns[0].justify = 1
TABLE.rows[1].columns[0].justify = 1
TABLE.rows[2].columns[0].justify = 1
TABLE.rows[0].columns[2].justify = 2
TABLE.rows[1].columns[2].justify = 2
TABLE.rows[2].columns[2].justify = 2
TABLE.rows[0].columns[1].width = 15
TABLE.rows[1].columns[1].width = 15
TABLE.rows[2].columns[1].width = 15

TABLE.draw()

print(f"FlyweightFactory has {FlyweightFactory.get_count()} flyweights")

./flyweight/flyweight.py

1
2
3
4
5
6
7
"The Flyweight that contains an intrinsic value called code"

class Flyweight():  # pylint: disable=too-few-public-methods
    "The Flyweight that contains an intrinsic value called code"

    def __init__(self, code: int) -> None:
        self.code = code

./flyweight/flyweight_factory.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"Creating the FlyweightFactory as a singleton"
from flyweight import Flyweight

class FlyweightFactory():
    "Creating the FlyweightFactory as a singleton"

    _flyweights: dict[int, Flyweight] = {}

    def __new__(cls):
        return cls

    @classmethod
    def get_flyweight(cls, code: int) -> Flyweight:
        "A static method to get a flyweight based on a code"
        if not code in cls._flyweights:
            cls._flyweights[code] = Flyweight(code)
        return cls._flyweights[code]

    @classmethod
    def get_count(cls) -> int:
        "Return the number of flyweights in the cache"
        return len(cls._flyweights)

./flyweight/column.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"A Column that is used in a Row"

from flyweight_factory import FlyweightFactory

class Column():  # pylint: disable=too-few-public-methods
    """
    The columns are the contexts.
    They will share the Flyweights via the FlyweightsFactory.
    `data`, `width` and `justify` are extrinsic values. They are outside
    of the flyweights.
    """

    def __init__(self, data="", width=11, justify=0) -> None:
        self.data = data
        self.width = width
        self.justify = justify  # 0:center, 1:left, 2:right

    def get_data(self):
        "Get the flyweight value from the factory, and apply the extrinsic values"
        ret = ""
        for data in self.data:
            ret = ret + FlyweightFactory.get_flyweight(data).code
        ret = f"{ret.center(self.width)}" if self.justify == 0 else ret
        ret = f"{ret.ljust(self.width)}" if self.justify == 1 else ret
        ret = f"{ret.rjust(self.width)}" if self.justify == 2 else ret
        return ret

./flyweight/row.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"A Row in the Table"
from column import Column

class Row():  # pylint: disable=too-few-public-methods
    "A Row in the Table"

    def __init__(self, column_count: int) -> None:
        self.columns = []
        for _ in range(column_count):
            self.columns.append(Column())

    def get_data(self):
        "Format the row before returning it to the table"
        ret = ""
        for column in self.columns:
            ret = f"{ret}{column.get_data()}|"
        return ret

./flyweight/table.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"A Formatted Table that includes rows and columns"

from row import Row

class Table():  # pylint: disable=too-few-public-methods
    "A Formatted Table"

    def __init__(self, row_count: int, column_count: int) -> None:
        self.rows = []
        for _ in range(row_count):
            self.rows.append(Row(column_count))

    def draw(self):
        "Draws the table formatted in the console"
        max_row_length = 0
        rows = []
        for row in self.rows:
            row_data = row.get_data()
            rows.append(f"|{row_data}")
            row_length = len(row_data) + 1
            if max_row_length < row_length:
                max_row_length = row_length
        print("-" * max_row_length)
        for row in rows:
            print(row)
        print("-" * max_row_length)

Output

1
2
3
4
5
6
7
python ./flyweight/client.py    
-----------------------------------------
|abra       |     112233    |    cadabra|
|racadab    |     12345     |     332211|
|cadabra    |     445566    |   aa 22 bb|
-----------------------------------------
FlyweightFactory has 12 flyweights

New Coding Concepts

String Justification

In ./flyweight/column.py, there are commands center(), ljust() and rjust() .

These are special commands on strings that allow you to pad strings and align them left, right, center depending on total string length.

eg,

1
2
>>> "abcd".center(10)
'   abcd   '
1
2
>>> "abcd".rjust(10)  
'      abcd'
1
2
>>> "abcd".ljust(10) 
'abcd      '

Summary

...Refer to Book or Videos for extra content.