April 18, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working with Design Patterns: Flyweight

  • May 14, 2007
  • By Jeff Langr
  • Send Email »
  • More Articles »

The original Design Patterns book contains 23 patterns that identify named solutions to common software development problems. Over the years, I've found a need for many of these patterns over and over again. I'm continually recognizing patterns such as the command and template method. I've found only minimal use for some of the patterns, however, such as flyweight. Yet, flyweight is a design pattern that Java itself heavily depends upon.

Wikipedia indicates that the flyweight pattern is appropriate when "many objects must be manipulated and these cannot afford to have extraneous data." In Java, String objects are managed as flyweight. Java puts all fixed String literals into a literal pool. For redundant literals, Java keeps only one copy in the pool. I can demonstrate this with a simple "language test" written in JUnit:

@Test
public void pool() {
   String author = "Henry David Thoreau";
   String authorCopy = "Henry David Thoreau";
   assertTrue(author.equals(authorCopy));
   assertTrue(author == authorCopy);
}

The two String objects are, of course, semantically equal because their contents are the same. I demonstrate the equality by calling .equals() in the first assertion. The two objects are also located in the same location in memory! The second assertion compares the references (addresses) of author and authorCopy.

Even though the two String objects are created separately, under the covers Java is storing them in the same location, to save space. Most applications heavily use Strings. The payback can be significant: Profiling reveals that String objects often represent anywhere from a third to a half of all objects created in a typical application.

As an aside, the use of flyweight in Java to minimize String storage often causes beginning programmers to make a dangerous mistake. When comparing two references to determine whether or not they hold the same character sequences, novice Java programmers will often code:

if (author == authorCopy)

As our test demonstrates, the comparison can work—it can return true even if the two objects were created at separate times during execution of the Java application. Unfortunately, Java does not automatically put dynamically created Strings into the literal pool. The last assertion in the following test fails:

@Test
public void pool() {
   String author = "Henry David Thoreau";
   String authorCopy = "Henry David Thoreau";
   assertTrue(author.equals(authorCopy));
   assertTrue(author == authorCopy);

   StringBuilder builder = new StringBuilder("Henry");
   builder.append(" David Thoreau");
   assertTrue(author.equals(builder.toString()));
   // this assertion fails!
   assertTrue(author == builder.toString());
}

The String literal "Henry David Thoreau" appears in the literal pool because Java was able to extract it during compile time. However, at compile time, Java cannot guarantee that execution of the code would result in " David Thoreau" getting concatenated to the "Henry" literal. Theoretically, the compiler could figure things out in this case, but that would dramatically increase compile time. And, such a tactic would work only in the simplest cases. (You can force a dynamically created string into the literal pool by creating a new String using String's .intern() method.)

The chief lesson? You should almost always compare String references using .equals.

Time Objects

I've been working on a scheduling application that supports appointments. It contains a Time class that encapsulates a time of day, expressed using hour (presuming a 24-hour clock) and minute.

public class Time {
   private byte hour;
   private byte minute;

   public Time(byte hour, byte minute) {
      this.hour = hour;
      this.minute = minute;
   }

   public byte hour() {
      return hour;
   }

   public byte minute() {
      return minute;
   }

   public String toString() {
      return hour + ":" + minute;
   }
}

My application must support very large numbers of appointments, perhaps millions, and I'm concerned about the memory requirement. The reality is, however, that appointments during a business day typically start either on the hour, or at quarter hour intervals past the hour. If I use the flyweight pattern so that I only store unique Time instances, I can reduce the maximum number of typical time objects to 96 (24 hours times four possible times per hour). The appointment application will still support odd times, but predictable use suggests that flyweight will dramatically minimize object creation and memory usage.

The key to making flyweight work is by controlling object instantiation using a factory method. The job of a factory method is simply to create objects: given input criteria, return an object of appropriate type. I design a TimeFactory class by writing a simple unit test:

import static org.junit.Assert.*;
import org.junit.*;

public class TimeFactoryTest {
   private static final byte HOUR = 23;
   private static final byte MINUTE = 59;

   @Test
   public void create() {
      Time time = TimeFactory.create(HOUR, MINUTE);
      assertEquals("23:59", time.toString());
   }
}

The code to get this test passing is trivial:

public class TimeFactory {
   public static Time create(byte hour, byte minute) {
      return new Time(hour, minute);
   }
}

The stage is set for a second test:

@Test
public void reuseOfMemory() {
   Time time1 = TimeFactory.create(HOUR, MINUTE);
   Time time2 = TimeFactory.create(HOUR, MINUTE);
   assertSame(time1, time2);
}

In other words: If I create two time objects, each with the same hour and minute, they should be the same object in memory. JUnit's assertSame method is equivalent to stating:

assertTrue(time1 == time2);

in this example. The test fails because the create method returns distinct Time instances. The failing reuseOfMemory test drives me to change the implementation in TimeFactory:

import java.util.*;

public class TimeFactory {
   private static Map<String,Time> times = new HashMap<String,Time>();
   public static Time create(byte hour, byte minute) {
      String key = hour + ":" + minute;
      Time time = times.get(key);
      if (time == null) {
         time = new Time(hour, minute);
         times.put(key, time);
      }
      return time;
   }
}




Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel