© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_10

10. Flyweight Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

This chapter covers the flyweight pattern.

GoF Definition

Use sharing to support large numbers of fine-grained objects efficiently.

Concept

In their famous book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995), the Gang of Four (GoF) wrote about flyweights as follows:

A flyweight is a shared object that can be used in multiple contexts simultaneously. The flyweight acts as an independent object in each context—it’s indistinguishable from an instance of the object that’s not shared. Flyweights cannot make assumptions about the context in which they operate.

When you consider flyweight pattern, you need to remember following points:
  • The pattern is useful when you need a large number of similar objects that are unique in terms of only a few parameters and most of the stuffs are common in general.

  • A flyweight is an object. It tries to minimize memory usage by sharing data as much as possible with other similar objects. Sharing objects may allow their usage at fine granularities with minimum costs.

  • Two common terms are used in this context: extrinsic and intrinsic. An intrinsic state is stored/shared in the flyweight object, and it is independent of flyweight’s context. On the other hand, an extrinsic state varies with flyweight’s context, which is why they cannot be shared. Client objects maintain the extrinsic state, and they need to pass this to a flyweight. Note that, if required, clients can also compute the extrinsic state on the fly when using flyweights.

  • Experts suggest that while implementing this pattern, we should make intrinsic states immutable.

Real-World Example

Suppose that you have pen. You can replace different refills to write with different colors. So, a pen without refills is considered as a flyweight with intrinsic data, and a pen with refills is considered as extrinsic data.

Consider another example. Suppose that a company needs to print visiting cards for its employees. So, where does the process start? The company can create a common template with the company logo, address, and so forth (intrinsic), and later it adds each employee’s particular contact information (extrinsic) on the cards.

Computer-World Example

Suppose that you want to make a website where different users can compile and execute the programs with their preferred computer languages, such as Java, C++, C#, and so forth. If you need to set up a unique environment for each individual user within a short period of time, your site will overload and the response time of the server will become so slow that no one will be interested in using your site. So, instead of creating a new programming environment for every user, you can make a common programming environment (which supports different programming language with/without minor changes) among them. And to check the existing/available programming environment and to make decisions whether you need to create a new one or not, you can maintain a factory.

Consider another example. Suppose that in a computer game, you have large number of participants whose core structures are same, but their appearances vary (e.g., different states, colors, weapons, etc.) Therefore, assume that if you need to create (or store) all of these objects with all of these variations/states, the memory requirement will be huge. So, instead of storing all of these objects, you can design your application in such way that you create these instances with common properties (flyweights with intrinsic state) and your client object maintains all of these variations (extrinsic states). If you can successfully implement this concept, you can claim that you have followed the flyweight design pattern in your application.

Another common use of this pattern is seen in the graphical representation of characters in a word processor.

Note

In Java, you may notice the use of this pattern when you use the wrapper classes, such as java.lang.Integer, java.lang.Short, java.lang.Byte, and java.lang.Character, where the static method valueof() replicates a factory method. (It is worth remembering that some of the wrapper classes, such as java.lang.Double and java.lang.Float, do not follow this pattern.) The String pool is another example of a flyweight.

Illustration

In the following example, I used three different types of objects: small, large, and fixed-size robots. These robots have two states: “robotTypeCreated” and “color”. The first one can be shared among “similar” objects, so it is an intrinsic state. The second one (color) is supplied by the client and it varies with the context. So, it is an extrinsic state in this example.

For the fixed-size robots, it does not matter which color is supplied by the client. For these robots, I am ignoring the extrinsic state, so you can conclude that these fixed-size robots are representing unshared flyweights .

In this implementation, the robotFactory class caches these flyweights and provides a method to get them.

Lastly, these objects are similar. So, once a particular robot is created, you do not want to repeat the process from scratch. Instead, the next time onward, you will try to use these flyweights to serve your needs. Now go through the code with the comments for your ready reference.

Class Diagram

Figure 10-1 shows the class diagram.
../images/395506_2_En_10_Chapter/395506_2_En_10_Fig1_HTML.jpg
Figure 10-1

Class diagram

Package Explorer View

Figure 10-2 shows the high-level structure of the program.
../images/395506_2_En_10_Chapter/395506_2_En_10_Fig2_HTML.jpg
Figure 10-2

Package Explorer view

Implementation

Here’s the implementation.
package jdp2e.flyweight.demo;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
interface Robot
{
    //Color comes from client.It is extrinsic.
    void showMe(String color);
}
//A shared flyweight implementation
class SmallRobot implements Robot
{
    /*
     * Intrinsic state.
     * It is not supplied by client.
     * So, it is independent of the flyweight’s context.
     * This can be shared across.
     * These data are often immutable.
     */
    private final String robotTypeCreated;
    public SmallRobot()
    {
           robotTypeCreated="A small robot created";
           System.out.print(robotTypeCreated);
    }
    @Override
    public void showMe(String color)
    {
        System.out.print(" with " +color + " color");
    }
}
//A shared flyweight implementation
class LargeRobot implements Robot
{
    /*
     * Intrinsic state.
     * It is not supplied by client .
     * So, it is independent of the flyweight’s context.
     * This can be shared across.
     * These data are often immutable.
     */
    private final String robotTypeCreated;
    public LargeRobot()
    {
        robotTypeCreated="A large robot created";
        System.out.print(robotTypeCreated);
    }
    @Override
    public void showMe(String color)
    {
        System.out.print(" with " + color + " color");
    }
}
//An unshared flyweight implementation
class FixedSizeRobot implements Robot
{
    /*
     * Intrinsic state.
     * It is not supplied by client.
     * So, it is independent of the flyweight’s context.
     * This can be shared acorss.
     */
    private final String robotTypeCreated;
    public FixedSizeRobot()
    {
        robotTypeCreated="A robot with a fixed size created";
        System.out.print(robotTypeCreated);
    }
    @Override
    //Ingoring the extrinsic state argument
    //Since it is an unshared flyweight
    public void showMe(String color)
    {
        System.out.print(" with " + " blue (default) color");
    }
}
class RobotFactory
{
    static Map<String, Robot> robotFactory = new HashMap<String, Robot>();
    public int totalObjectsCreated()
    {
        return robotFactory.size();
    }
    public static synchronized Robot getRobotFromFactory(String robotType) throws Exception
    {
        Robot robotCategory = robotFactory.get(robotType);
        if(robotCategory==null)
        {
            switch (robotType)
            {
            case "small":
                System.out.println("We do not have Small Robot at present.So we are creating a small robot now.") ;
                robotCategory = new SmallRobot();
                break;
            case "large":
                System.out.println("We do not have Large Robot at present.So we are creating a large robot now.");
                robotCategory = new LargeRobot();
                break;
            case "fixed":
                System.out.println("We do not have fixed size at present. So we are creating a fixed size robot now.");
                robotCategory = new FixedSizeRobot();
                break;
            default:
                throw new Exception(" Robot Factory can create only small ,large or fixed size robots");
            }
            robotFactory.put(robotType,robotCategory);
        }
        else
        {
            System.out.print(" Using existing "+ robotType +" robot and coloring it" );
        }
        return robotCategory;
    }
}
public class FlyweightPatternExample {
    public static void main(String[] args) throws Exception {
        RobotFactory robotFactory = new RobotFactory();
        System.out.println(" ***Flyweight Pattern Example *** ");
        Robot myRobot;
        //Here we are trying to get 3 Small type robots
        for (int i = 0; i < 3; i++)
        {
            myRobot = RobotFactory.getRobotFromFactory("small");
            /*
            Not required to add sleep().But it is included to
            increase the probability of getting a new random number
            to see the variations in the output .
             */
            Thread.sleep(1000);
            //The extrinsic property color is supplied by the client code.
            myRobot.showMe(getRandomColor());
        }
        int numOfDistinctRobots = robotFactory.totalObjectsCreated();
        System.out.println(" Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
        //Here we are trying to get 5 Large type robots
        for (int i = 0; i < 5; i++)
        {
            myRobot = RobotFactory.getRobotFromFactory("large");
            /*
            Not required to add sleep().But it is included to
            increase the probability of getting a new random number
            to see the variations in the output.
             */
            Thread.sleep(1000);
            //The extrinsic property color is supplied by the client code.
            myRobot.showMe(getRandomColor());
        }
        numOfDistinctRobots = robotFactory.totalObjectsCreated();
        System.out.println(" Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
        //Here we are trying to get 4 fixed sizerobots
        for (int i = 0; i < 4; i++)
        {
            myRobot = RobotFactory.getRobotFromFactory("fixed");
            /*
            Not required to add sleep().But it is included to
            increase the probability of getting a new random number
            to see the variations in the output.
             */
            Thread.sleep(1000);
            //The extrinsic property color is supplied by the client code.
            myRobot.showMe(getRandomColor());
        }
        numOfDistinctRobots = robotFactory.totalObjectsCreated();
        System.out.println(" Till now, total no of distinct robot objects created: " + numOfDistinctRobots);
    }
    static String getRandomColor()
    {
        Random r = new Random();
        /* I am simply checking the random number generated that can be either an even number or an odd number. And based on that we are choosing the color. For simplicity, I am using only two colors-red and green
         */
        int random = r.nextInt();
        if (random % 2 == 0)
        {
            return "red";
        }
        else
        {
            return "green";
        }
    }
}

Output

Here’s the first run output.
***Flyweight Pattern Example ***
We do not have Small Robot at present.So we are creating a small robot now.
A small robot created with green color
      Using existing small robot and coloring it with green color
      Using existing small robot and coloring it with red color
 Till now, total no of distinct robot objects created: 1
We do not have Large Robot at present.So we are creating a large robot now.
A large robot created with green color
      Using existing large robot and coloring it with red color
      Using existing large robot and coloring it with green color
      Using existing large robot and coloring it with green color
      Using existing large robot and coloring it with green color
 Till now, total no of distinct robot objects created: 2
We do not have fixed size at present.So we are creating a fixed size robot now.
A robot with a fixed size created with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
 Till now, total no of distinct robot objects created: 3
Here’s the second run output.
***Flyweight Pattern Example ***
We do not have Small Robot at present.So we are creating a small robot now.
A small robot created with red color
      Using existing small robot and coloring it with green color
      Using existing small robot and coloring it with green color
 Till now, total no of distinct robot objects created: 1
We do not have Large Robot at present.So we are creating a large robot now.
A large robot created with red color
      Using existing large robot and coloring it with green color
      Using existing large robot and coloring it with green color
      Using existing large robot and coloring it with red color
      Using existing large robot and coloring it with green color
 Till now, total no of distinct robot objects created: 2
We do not have fixed size at present.So we are creating a fixed size robot now.
A robot with a fixed size created with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
      Using existing fixed robot and coloring it with  blue (default) color
 Till now, total no of distinct robot objects created: 3

Analysis

  • The output varies because in this implementation, I am choosing color at random.

  • The fixed-size robot’s color never changes because the extrinsic state (color) is ignored to represent an unshared flyweight.

  • The client needed to play with 12 robots (3 small, 5 large, 4 fixed-size) but these demands are served by only three distinct template objects (one from each category) and these were configured on the fly.

Q&A Session

  1. 1.

    I notice some similarities between a singleton pattern and a flyweight pattern. Can you highlight the key differences between them?

    The singleton pattern helps you maintain only one required object in the system. In other words, once the required object is created, you cannot create more. You need to reuse the existing object.

    The flyweight pattern is generally concerned about a large number of similar (which can be heavy) objects, because they may occupy big blocks of memory. So, you try to create a smaller set of template objects that can be configured on the fly to complete the creation of the heavy objects. These smaller and configurable objects are called flyweights. You can reuse them in your application to appear that you have many large objects. This approach helps you reduce the consumption of big chunks of memory. Basically, flyweights make one look like many. This is why the GoF tells us: A flyweight is a shared object that can be used in multiple contexts simultaneously. The flyweight acts as an independent object in each context — it’s indistinguishable from an instance of the object that’s not shared.

    Figure 10-3 visualizes the core concepts of the flyweight pattern before using flyweights.
    ../images/395506_2_En_10_Chapter/395506_2_En_10_Fig3_HTML.jpg
    Figure 10-3

    Before using flyweights

    Figure 10-4 shows the design after using flyweights.
    ../images/395506_2_En_10_Chapter/395506_2_En_10_Fig4_HTML.jpg
    Figure 10-4

    After using flyweights

    In Figure 10-4, you can see that
    • Heavy Object 1 = Flyweight Object (shared) + Configuration 1 (extrinsic and not shared)

    • Heavy Object 2 = Flyweight Object(shared) + Configuration 2 (extrinsic and not shared)

     
By combining the intrinsic and extrinsic states, the flyweight objects provide the complete functionality.
  1. 2.

    Can you observe any impact due to multithreading?

    If you are creating objects with new operators in a multithreaded environment, you may end up with multiple unwanted objects (similar to singleton patterns). The remedy is similar to the way you handle multithreaded environment in a singleton pattern.

     
  2. 3.
    What are the advantages of using flyweight design patterns?
    • You can reduce memory consumptions of heavy objects that can be controlled identically.

    • You can reduce the total number of “complete but similar objects” in the system.

    • You can provide a centralized mechanism to control the states of many “virtual” objects.

     
  3. 4.
    What are the challenges associated with using flyweight design patterns?
    • In this pattern, you need to take the time to configure these flyweights. The configuration time can impact the overall performance of the application.

    • To create flyweights, you extract a common template class from the existing objects. This additional layer of programming can be tricky and sometimes hard to debug and maintain.

    • You can see that logical instances of a class cannot behave differently from other instances.

    • The flyweight pattern is often combined with singleton factory implementation and to guard the singularity, additional cost is required (e.g., you may opt for a synchronized method or double-checked locking, but each of them are costly operations).

     
  4. 5.

    Can I have non-shareable flyweight interface?

    Yes. A flyweight interface does not enforce that it needs to always be shareable. In some cases, you may have non-shareable flyweights with concrete flyweight objects as children. In our example, you saw the use of non-shareable flyweights using fixed-size robots.

     
  5. 6.

    Since intrinsic data of flyweights are the same, I can share them. Is this correct?

    Yes.

     
  6. 7.

    How do clients handle the extrinsic data of these flyweights?

    They need to pass the information (states) to the flyweights. Clients either manage the data or compute them on the fly.

     
  7. 8.

    Extrinsic data is not shareable. Is this correct?

    Yes.

     
  8. 9.

    You said that I should try to make intrinsic states immutable. How can I achieve that?

    Yes, for thread safety and security, experts suggest that you implement that. In this case, it is already implemented. In Java, you must remember that String objects are inherently immutable.

    Also, you may notice that in the concrete flyweights (SmallRobot, LargeRobot, FixedSizeRobot), there are no setter methods to set/modify the value of robotTypeCreated. When you supply the data only through a constructor and there are no setter methods, you are following an approach that promotes immutability.

     
  9. 10.

    You have tagged the final keyword with the intrinsic state robotTypeCreated to achieve immutability. Is this correct?

    You need to remember that final and immutability are not synonymous. In the context of design patterns, the word immutability generally means that once created, you cannot change the state of the object. Although the keyword final can be applied to a class, a method, or a field, the aim is different.

    The final field can help you construct a thread-safe immutable object without synchronization, and it provides safety in a multithreaded environment. So, I used it in this example. The concept is described in detail in the article at https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5-110 .

     
  10. 11.

    The getRobotFromFactory() method is synchronized here to provide thread safety. Is this understanding correct?

    Exactly. In a single-threaded environment, it is not required.

     
  11. 12.

    The getRobotFromFactory() method is static here. Is that mandatory?

    No. You can implement a non-static factory method also. You may often notice the presence of a singleton factory with flyweight pattern implementations.

     
  12. 13.

    What is the role of “RobotFactory” in this implementation?

    It caches flyweights and provides a method to get them. In this example, there are many objects that can be shared. So, storing them in a central place is always a good idea.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.197.95