August 28, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working With Design Patterns: Odds and Ends

  • September 18, 2008
  • By Jeff Langr
  • Send Email »
  • More Articles »

Loading an image is slow enough that you don't want to incur the cost unless necessary. Imagine that a search returns 500 books. If each image takes "only" 32ms to load (the time it took on my admittedly slow laptop), and the search is single threaded, a user will wait 16 seconds for the search to complete. Even if the search is multi-threaded, the loaded images will quickly bloat the memory space. A better solution is to load images only when the user needs to actually see them. Listing 3 shows an updated Book class that takes advantage of lazy initialization.

Listing 3: Using Lazy Initialization.

import java.awt.image.*;
import java.io.*;
import javax.imageio.*;

public class Book {
   private final String author;
   private final String title;
   private final String classification;
   private final String year;
   private BufferedImage coverImage;
   private final String imageFilename;

   public Book(String author, String title, String classification,
               String year, String imageFilename) {
      this.author = author;
      this.title = title;
      this.classification = classification;
      this.year = year;
      this.imageFilename = imageFilename;
   }

   public String getClassification() {
      return classification;
   }

   public String getAuthor() {
      return author;
   }

   public String getTitle() {
      return title;
   }

   public String getYear() {
      return year;
   }

   @Override
   public String toString() {
      return author + " " + title + " " + year + " " +
         classification;
   }

   public BufferedImage getCoverImage() {
      if (coverImage == null)
         loadCoverImage(imageFilename);
      return coverImage;
   }

   private void loadCoverImage(String imageFilename) {
      try {
         coverImage = ImageIO.read(new File(imageFilename));
      } catch (IOException e) {
         throw new RuntimeException(e);
      }
   }
}

The code within getCoverImage determines whether or not the coverImage field has already been initialized. If the field is null, the code calls off to loadCoverImage.

Thus, constructed books will not incur the cost of loading images, but that cost will be incurred when client code actually requests the image by calling getCoverImage. There's a slight overhead cost: Every time clients call getCoverImage, an additional check against coverImage executes. The overhead is almost never a problem, but it suggests that you probably don't want to use lazy initialization unless you really need it.

Lazy initialization requires client code to use the getter method. If the client code happens to be within the Book class itself, that means you must change any direct field access against coverImage to instead call the getter. This is a refactoring pattern known as Self-Encapsulate Field [Fowler].

How do you test-drive lazy initialization? There are at least three ideas:

  1. Don't. It will get tested indirectly by code that requires use of the image.
  2. Do. It's a performance optimization. Performance is a requirement for which you should write a test. In this case, you could time the call to the getter, and then assert that the code executes within an arbitrarily short period of time. The test should fail; then, you introduce the optimization to get it to pass. Note that you may have some challenges with execution variances on different platforms.
  3. Mock. Isolate the call to load the image to its own class, perhaps named ImageLoader. Inject an instance of this class into each Book object. Write a test to verify when the ImageLoader load operation gets invoked. This has the benefit of fixing the single-responsibility principle violation in Book—the work to load an image is a separate responsibility than tracking the attributes for a Book.

Object Pool

An object pool is a collection of objects that can be "re-used" on demand. Objects in the pool typically survive for the lifetime of an application. The goal of an object pool is to minimize re-creation of frequently created objects of the same type, so as to prevent a large number of objects from being created (which can help improve performance as well as minimize memory usage).

In Java, there are only rare occasions when you'll want to use an object pool. If you're looking to improve upon performance by avoiding garbage collection, understand that modern garbage collectors are extremely sophisticated. Your attempts to circumvent Java's garbage collector and manage memory on your own will most likely not provide the gains you're looking for, and can actually make things worse. Not to mention that re-using objects that have any real state is a tricky and risky proposition at best.

The typical reasons to use object pooling are to constrain threading and to minimize the number of open database connections. In either of these cases, or in other cases, the need to pool centers around some sort of real physical constraint.

Because thread pools and database connection pools are common needs, you'll find that you usually don't need to write your own. The Oracle JDBC drivers, for example, come with a connection pool class. And as of J2SE 1.5, Java contains support for thread pooling.

A key success factor in using object pools is to ensure that the state of each pool object gets reset when it is returned to the pool for future use. Otherwise you can begin to experience defects or memory leaks.

Rather than contrive a use for an object pool, I chose to contrive an implementation of a thread pool, even though the ThreadPoolExecutor is what you really want to use. This code example exists solely to demonstrate a minimalist implementation of a pool. Bells and whistles? It has none, not even a means to terminate gracefully.


Tags: Specification



Page 3 of 4



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel