Home | FAQ | Contact me

Decorator Pattern

Our goal in this example is to write succinct code. This isn't a specific goal of the decorator pattern, it's just an illustration of it.

What inhibits our goal, that of the code we're using to illustrate the decorator pattern, is that we're tempted to write code that checks for every possible thing that can go wrong. What we end up with is goopy code that wastes time and is hard to maintain. Here's our bad example:

public class Report
{
  void export( File file )
  {                        // what if...
    if( file == null )     //    caller's file isn't ever nil because he's careful?
      throw new IllegalArgumentException( "File is NULL" );
    if( file.exists() )    //    caller knows whether or not file exists?
      throw new IllegalArgumentException( "File would be overwritten" );

    // export report to file...
  }
}

The Decorator Pattern

Instead of writing this goopy code, let's make an interface and show the bare-bones implementation of what we really want to do, which always remains as tiny as it can be.

interface Report
{
  void export( File file );
}
public class DefaultReport implements Report
{
  @Override
  void export( File file )
  {
    // export report to file...
  }
}

Now, some consumers of Report are going to want help not making mistakes. Here are two decorated implementations that undertake the validations desired:

public class NoNullReport implements Report
{
  private final Report origin;

  public NoNullReport( Report report )
  {
    this.origin = report;
  }

  @Override
  public void export( File file )
  {
    if( file == null )
      throw new IllegalArgumentException( "File is NULL" );

    this.origin.export( file );
  }
}
public class NoOverwriteReport implements Report
{
  private final Report origin;

  public NoOverwriteReport( Report report )
  {
    this.origin = report;
  }

  @Override
  public void export( File file )
  {
    if( file.exists() )
      throw new IllegalArgumentException( "File would be overwritten" );

    this.origin.export( file );
  }
}

Finally, here's consuming both these decorators, because we choose to. What we do is wrap the essential-only implementation in successive decorative wrappers that keep us out of harm's way:

void writeReportToFile( File file )
{
  Report report = new NoNullReport( new NoOverwriteReport( new DefaultReport() ) );
  report.export( file );
}