3

Gang of Four’s Flyweight design pattern introduces the concept of intrinsic and extrinsic states:

The key concept here is the distinction between intrinsic and extrinsic state. Intrinsic state is stored in the flyweight; it consists of information that’s independent of the flyweight’s context, thereby making it sharable. Extrinsic state depends on and varies with the flyweight’s context and therefore can’t be shared. Client objects are responsible for passing extrinsic state to the flyweight when it needs it.

In other words, the state of an object can be decomposed with respect to a group of objects as an intrinsic state and an extrinsic state, where the intrinsic state is the intersection of the states of all objects of the group and the extrinsic state is the difference of the state of the object and the intrinsic state. Since the intrinsic state is duplicated in each object of the group, space can be saved by replacing the group of objects by a single flyweight object storing a single intrinsic state. The flyweight object cannot however store the multiple extrinsic states of the objects of the group, so the extrinsic states are stored outside and passed to the flyweight object in each request from client objects. Such an optimised communication protocol is often called a stateless protocol since the flyweight object does not store extrinsic state. Examples of stateless protocols include IP and HTTP (and more generally any REST protocols, where intrinsic state is called resource state and extrinsic state is called application state).

For instance, let’s take three objects with their respective clients:

o1 ← c1
o2 ← c2
o3 ← c3

We can decompose the state of each object with respect to the three objects:

state 1 = intrinsic stateextrinsic state 1
state 2 = intrinsic stateextrinsic state 2
state 3 = intrinsic stateextrinsic state 3

where:

intrinsic state = state 1state 2state 3
extrinsic state 1 = state 1 \ intrinsic state
extrinsic state 2 = state 2 \ intrinsic state
extrinsic state 3 = state 3 \ intrinsic state

Here the intrinsic state is duplicated. So storing it in a single flyweight object (and moving the extrinsic states into clients) saves space:

o ← c1, c2, c3

So far so good. Now I have been wondering if it is possible to classify attributes as intrinsic state or extrinsic state from the definition of an unshared class, in order to derive a flyweight class.

Is the following syntactic characterisation of intrinsic and extrinsic state for unshared classes correct?

  • intrinsic state = immutable instance variables ∪ class variables;
  • extrinsic state = mutable instance variables.

For instance, let’s define the following unshared class:

class Unshared:
    __z = 0

    def __init__(self):
        self.__x = 0
        self.__y = 0

    def f(self):
        return self.__x + self.__y

    def g(self):
        self.__y += 1

    @classmethod
    def h(cls):
        cls.__z += 1

The proposed characterisation would yield:

  • intrinsic state = {__x, __z};
  • extrinsic state = {__y}.

which seems correct. That unshared class can then be transformed into a flyweight class for saving space:

class Flyweight:
    __z = 0

    def __init__(self):
        self.__x = 0

    def f(self, y):
        return self.__x + y

    @classmethod
    def h(cls):
        cls.__z += 1

Edit

My characterisation was incomplete, as noted by @DocBrown, because it prevented mutable flyweight objects, by excluding mutable instance variables that are instance or class specific from the intrinsic state. And it also incorrectly included immutable instance variables that are instance specific in the intrinsic state.

Let’s define the following terms:

  • A group is a set of unshared objects from which a single flyweight object is derived.
  • An instance-scope instance variable is an instance variable that is specific to an individual object.
  • A lesser-group-scope instance variable is an instance variable that is common to a smaller group of unshared objects than the group under consideration.
  • A group-scope instance variable is an instance variable that is common to the group of unshared objects under consideration.
  • An greater-group-scope instance variable is an instance variable that is common to a larger group of unshared objects than the group under consideration.
  • A class-scope instance variable is an instance variable that is common to all objects.

An instance-scope instance variable and a lesser-group-scope instance variable are not sharable by the group of unshared objects under consideration, while a group-scope instance variable, greater-group-scope instance variable, a class-scope instance variable and a class variable are sharable by the group of unshared objects under consideration.

We can conclude that the complete characterisation of intrinsic and extrinsic state for unshared classes is the following:

  • intrinsic state = group-scope instance variables ∪ greater-group-scope instance variables ∪ class-scope instance variables ∪ class variables;
  • extrinsic state = instance-scope instance variables ∪ lesser-group-scope instance variables.

However as @DocBrown rightly pointed, this characterisation is not purely syntactic. Class variables, class-scope instance variables and some mutable instance-scope instance variables can be identified syntactically just by looking at the unshared class definition. But greater-group-scope instance variables, group-scope instance variables, lesser-group-scope instance variables, immutable instance-scope instance variables and mutable instance-scope instance variables can only be identified semantically.

When deriving a flyweight class from an unshared class, its instance-scope instance variables and lesser-group-scope instance variables are moved outside of the flyweight class, its group-scope instance variables become instance-scope instance variables of the flyweight class, its greater-group-scope instance variables are moved outside of the flyweight class, its class-scope instance variables remain class-scope instance variables of the flyweight class, and its class variables remain class variables of the flyweight class.

For instance, let’s define the following unshared class:

import collections

class Unshared:
    __L = 0  # immutable class variable
    __instances = collections.defaultdict(list)  # mutable class variable

    def __init__(self, GROUP, I, i, j, k):
        self.__instances[GROUP].append(self)
        self.__GROUP = GROUP  # immutable group-scope instance variable
        self.__I = I  # immutable instance-scope instance variable
        self.__i = i  # mutable instance-scope instance variable
        for instance in self.__instances[GROUP]:
            instance.__j = j  # mutable group-scope instance variable
        self.__K = 0  # immutable class-scope instance variable
        for instances in self.__instances.values():
            for instance in instances:
                instance.__k = k  # mutable class-scope instance variable

    def query(self):
        return {
            "instance": [self.__I, self.__i],
            "group": [self.__GROUP, self.__j],
            "class": [self.__K, self.__k, self.__L, tuple(self.__instances)]
        }

    def manipulate(self):
        # Update the mutable instance-scope variables.
        self.__i = None
        # Update the mutable group-scope variables.
        for instance in self.__instances[self.__GROUP]:
            instance.__j = None
        # Update the mutable class-scope variables.
        for instances in self.__instances.values():
            for instance in instances:
                instance.__k = None
        self.__instances[None] = []

The new characterisation yields:

  • intrinsic state = {__GROUP, __j, __K, __k, __L, __instances};
  • extrinsic state = {__I, __i}.

That unshared class can then be transformed into a flyweight class for saving space:

class Flyweight:
    __L = 0
    __instances = {}

    def __init__(self, GROUP, j, k):
        self.__instances[GROUP] = self
        self.__GROUP = GROUP
        self.__j = j
        self.__K = 0
        for instance in self.__instances.values():
            instance.__k = k

    def query(self, I, i):
        return {
            "instance": [I, i],
            "group": [self.__GROUP, self.__j],
            "class": [self.__K, self.__k, self.__L, tuple(self.__instances)]
        }

    def manipulate(self):
        # Update the mutable group-scope variables.
        self.__j = None
        # Update the mutable class-scope variables.
        for instance in self.__instances.values():
            instance.__k = None
        self.__instances[None] = []

Unshared class usage:

ra1 = Unshared("a", 1, 1, 1, 0)  # unshared instance of group "a"
ra2 = Unshared("a", 2, 2, 1, 0)  # unshared instance of group "a"
rb1 = Unshared("b", 1, 1, 2, 0)  # unshared instance of group "b"
rb2 = Unshared("b", 2, 2, 2, 0)  # unshared instance of group "b"

print(ra1.query())  # {"instance": [1, 1], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(ra2.query())  # {"instance": [2, 2], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(rb1.query())  # {"instance": [1, 1], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
print(rb2.query())  # {"instance": [2, 2], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
ra1.manipulate()
print(ra1.query())  # {"instance": [1, None], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(ra2.query())  # {"instance": [2, 2], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(rb1.query())  # {"instance": [1, 1], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
print(rb2.query())  # {"instance": [2, 2], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}

Flyweight class usage:

fa = Flyweight("a", 1, 0)  # shared instance of group "a"
fb = Flyweight("b", 2, 0)  # shared instance of group "b"

print(fa.query(1, 1))  # {"instance": [1, 1], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(fa.query(2, 2))  # {"instance": [2, 2], "group": ["a", 1], "class": [0, 0, 0, ("a", "b")]}
print(fb.query(1, 1))  # {"instance": [1, 1], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
print(fb.query(2, 2))  # {"instance": [2, 2], "group": ["b", 2], "class": [0, 0, 0, ("a", "b")]}
fa.manipulate()
print(fa.query(1, None))  # {"instance": [1, None], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(fa.query(2, 2))     # {"instance": [2, 2], "group": ["a", None], "class": [0, None, 0, ("a", "b", None)]}
print(fb.query(1, 1))     # {"instance": [1, 1], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
print(fb.query(2, 2))     # {"instance": [2, 2], "group": ["b", 2], "class": [0, None, 0, ("a", "b", None)]}
6
  • 1
    There are some good examples here, both in code and in UML: en.wikipedia.org/wiki/Flyweight_pattern. There's more than one way to implement a flyweight; sharing class variables is one way. Commented Jan 3, 2020 at 3:21
  • Extrinsic state for a flyweight object has to be stored outside the object itself, so it must not be an instance variable of the object (or maybe I misunderstood something in your example?). Please clarify.
    – Doc Brown
    Commented Jan 3, 2020 at 12:46
  • @DocBrown Actually my question is about objects prior to applying the flyweight optimisation (AKA stateless protocol). So the extrinsic state is still part of the object, it has not been moved to the clients.
    – Géry Ogam
    Commented Jan 3, 2020 at 13:14
  • Too lazy to write an answer right now, but I'll share these two thoughts: (1) In the flyweight pattern, the intrinsic state is actually immutable (link) and is of course the same for everyone. (2) Extracting the portion of the object that is to go into a flyweight is not unlike normalizing a database table, if that helps. link.
    – John Wu
    Commented Jan 4, 2020 at 1:30
  • @JohnWu GoF’s book, which is the reference, never states that flyweights are immutable.
    – Géry Ogam
    Commented Jan 5, 2020 at 23:50

2 Answers 2

2

If your question is if

instance variables that are updatable from the interface

are candidates for becoming extrinsic state, whilst

class variables + instance variables that are not updatable from the interface

are candidates for becoming intrinsic state of a flyweight object, I would say that it may be sometimes the case, but not necessarily:

  • creating a new object from instance variables which are not updatable from the interface leads to an immutable flyweight object. That is actually a good way to avoid unwanted side effects, since those objects will a shared among different clients;

  • class variables are already shared, so they provide already intrinsic state.

However, this is not a hard rule: you could also have requirements where it makes sense to create flyweights with deliberate side effects to all clients. For example, the Python example in Wikipedia is about immutable cheese brands, where each brand has a fixed cost. But if one gets a requirement to change the costs of all brands afterwards, making the flyweight objects mutable could be a possible solution.

Moreover, don't forget the idea of the flyweight is to save a notable amount of memory space. If making the intrinsic state shared does not help to reduce the number of objects in a relevant fashion, or reducing the combinations of extrinsic state, then just looking at the kind of instance variables does not help.

Note also, the class in stake might have to be restructured internally before trying to extract a flyweight out of it, which makes looking just at the currently existing instance variables or their usage through the class interface pointless.

Let me finally say that such design questions are seldom answerable when given just class names Regular and method names f, g and h. One needs to have some context, need to understand the specific use case of the classes, and then one can decide on a semantical basis what to do. Trying to approach such a problem only from a purely syntactical point of view is usually not working well.

6
  • Thanks for answering. I have edited my post a little bit to make the question clearer. "you could also have requirements where it makes sense to create flyweights with deliberate side effects to all clients." That is a very good point! It means that my intrinsic state characterisation for original objects is not exhaustive as it excludes instance variables that are updatable from the interface and somehow synchronised for all clients, which is vague. Now I see how trying to characterise intrinsic state for the original objects instead of the derived flyweight object may not be the best idea.
    – Géry Ogam
    Commented Jan 4, 2020 at 3:12
  • I am going to accept your answer. But before I do, could you add to it an example of a mutable flyweight and maybe polish a little bit the formatting?
    – Géry Ogam
    Commented Jan 6, 2020 at 2:33
  • 1
    @Maggyero: better now?
    – Doc Brown
    Commented Jan 6, 2020 at 12:23
  • The more I edit my post, the more I find this answer brilliant.
    – Géry Ogam
    Commented Apr 6, 2020 at 23:06
  • @Maggyero: don't get me wrong, but I don't think the additional text really improves the question it.The whole example is still created of meaningless variables and terms "a, b, c ...", which makes it pretty hard to read. A a huge wall-of-text does not really motivate a reader to follow it to the end. Brevity is a virtue.
    – Doc Brown
    Commented Apr 7, 2020 at 8:09
0

Your understanding is basically correct. An example of the proper use of Flyweight model would be with Java Swing's JTable and the cell renderer (example). The idea is that there are a finite number of cell renderers. Information from the table is passed in to the renderer to calculate what should be done there.

In this example, all the extrinsic state is passed in to a callback method:

Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
    // .... Can set color based on row, whether the cell has focus, etc.
    // .... all of that is extrinsic state
}

In this example it's pretty rare for the flyweight to have intrinsic state. And I think that is important to understand with this pattern. However, examples of intrinsic state might include:

  • Set of icons to switch between depending on extrinsic state
  • Set of fonts to switch between depending on which row you are in

The whole concept behind the flyweight it to minimize instances of the flyweight while maximizing the reusability of it.


With the Gang of Four patterns book, their example was for glyphs on a page. Rendering individual glyphs would be memory intensive if each character in a document had it's own glyph object. In their example they split the responsibility this way:

  • Each glyph object had the intrinsic value of the glyph graphics
  • The extrinsic value was the location on screen (or paper) where the glyph was to be rendered

In many ways, that's how fonts are rendered on screen. The Font has a definition of each glyph in a character set, and the screen renders those glyphs as needed.

0

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .