Sunday, 14 January 2018

Design Patterns | Flyweight Pattern

The objective of this pattern is to use sharing to support a large number of objects that have part of their internal state in common where the other part of the state can vary.

A flyweight is an object that minimizes memory usage by sharing the possible data with other similar objects. It is a way to use objects in large numbers when a simple repeated representation would use an unwanted amount of memory. Often some parts of the object state can be shared, and it is common practice to hold them in external data structures and pass them to the objects temporarily when they are used.

Real Life Examples
Modern web browser
Web browsers use this technique to prevent loading same images twice. When the browser loads a web page, it traverses through all images on that web page and loads all new images from the Internet and places them the internal cache. For already loaded images, a flyweight object is created, which has some unique data like position within the page, but everything else is referenced to the cached one.

Counter-Strike
In the counter strike, there are a large number of player objects and a player object contains common properties like activity of the player, type of player. There are some additional properties like firing weapons. Creating a large number of player objects is a necessity however it would incur a huge memory cost. Note that although the activity and name of a player are the same, their weapons can vary greatly. The solution is to keep the common state of soldiers in a shared object and inject the additional properties.

How to reuse the object those are already created?
1. Reuse the object that already existing (earlier created and stored)
2. Create the new object when no matching object is found.

Similarities between objects are stored inside of the objects (intrinsic), and differences are moved outside of the objects and placed in the client code (extrinsic).


package com.algorithm;

/** FlyWeight Pattern implementation in the Counter Strike Game.*/
import java.util.Random;
import java.util.HashMap;

/** Interface for all players */
interface Player {

      /** To set the Extrinsic Attribute. */
      public void allocateWeapon(String weapon);

      /** Work on the Mission */
      public void mission();
}

/** Every Terrorist has been the weapon (as Extrinsic Attribute) and mission. */
class Terrorist implements Player {

      /** Intrinsic Attribute. */
      private final String activity;

      /** Extrinsic Attribute. */
      private String weapon;
     
      private String player;

      public Terrorist(String player) {
            this.player = player;
            this.activity = "bombing";
      }

      @Override
      public void allocateWeapon(String weapon) {
            this.weapon = weapon;
      }

      @Override
      public void mission() {
            System.err.println(player + " with '" + weapon + "' and his task is to '" + activity+"'");
      }
}

/**
 * Every CounterTerrorist has been weapon (as Extrinsic Attribute) and mission.
 */
class CounterTerrorist implements Player {
      /** Intrinsic Attribute. */
      private final String activity;

      /** Extrinsic Attribute. */
      private String weapon;

      private String player;

      public CounterTerrorist(String player) {
            this.player = player;
            this.activity = "prevent bombing";
      }

      @Override
      public void allocateWeapon(String weapon) {
            this.weapon = weapon;
      }

      @Override
      public void mission() {
            System.out.println(player + " with '" + weapon + "' and his task is to '" + activity+"'");
      }
}

/**
 * A factory class which produces the player and returns an existing player
 * If the player given type exists in the players' map.
 * otherwise it creates a new player, put into the cached map and returns it.
 */
class CounterPlayerFactory {
     
      /** HashMap stores the reference Terrorist or CounterTerrorist objects. */
      private static HashMap<String, Player> playersMap = new HashMap<String, Player>();

      /** Factory method to get a player. */
      public static Player getPlayer(String type) {
            Player player = null;

            /** If an object for Terrorist or CounterTerrorist has already been
             created simply return its reference. */
            if (playersMap.containsKey(type)) {
                  player = playersMap.get(type);
            } else {
                  /** Create an object of Terrorist/CounterTerrorist */
                  switch (type) {
                        case "Terrorist":
                              System.out.println("Terrorist Created");
                              player = new Terrorist(type);
                              break;
                        case "CounterTerrorist":
                              System.out.println("Counter Terrorist Created");
                              player = new CounterTerrorist(type);
                              break;
                        default:
                              System.err.println("Wrong Choice!");
                  }

                  /** Created insert it into the HashMap .*/
                  System.out.println("Put the "+type+" into the Map");
                  playersMap.put(type, player);
            }
            return player;
      }
}

public class CounterStrikeTest {

      /** Utility methods to get a random player type or weapon. */
      public static String getRandPicker(String[] options) {
            int size = options.length;
            Random rand = new Random();
                       
            /** Return the player stored at index 'randInt' */
            return options[rand.nextInt(size)];
      }
     
      /** players and weapons. */
      private static String[] players = { "Terrorist", "CounterTerrorist" };
      private static String[] weapons = { "AK-47", "M4A1-S", "Gut Knife", "Krieg 552" };

      public static void main(String args[]) {
           
            /** Assuming that there are 12 players in the game. */
            for (int i = 0; i < 12; i++) {
                  /** getPlayer() is called to return the player. */
                  Player player = CounterPlayerFactory.getPlayer(getRandPicker(players));

                  /** Assign a weapon to the player. */
                  player.allocateWeapon(getRandPicker(weapons));

                  /** Send this player on a mission. */
                  player.mission();
            }
      }
}

What problems can the Flyweight design pattern solve?
Large numbers of objects should be supported efficiently.
Creating large numbers of objects should be avoided.

What solution does the Flyweight design pattern describe?
Define Flyweight objects that
Store intrinsic (invariant) state that can be shared and
Provide an interface through which extrinsic (variant) state can be passed in.

Why do we care about the number of objects in our program?
The excess number of objects may lead to errors related to memory like java.lang.OutOfMemoryError less number of objects reduces the memory. Although creating an object in Java is really fast, we can still reduce the execution time of our program by sharing objects.

Advantages of Flyweight pattern
It reduces the number of objects. This can be used to reduce memory requirements and object creational cost. It also reduces database storage devices required if the objects are persisted.

Flyweight Pattern Example in JDK
1. All the valueOf() method of all wrapper classes uses cached objects showing the use of Flyweight design pattern.
2. The best example is Java String class String Pool implementation.

Important Point
One important feature of flyweight objects is that they are immutable. This means that they cannot be modified once they have been constructed.


No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...