The Java Enclosing Class as an Implementation of the Builder Pattern

Some language features are just plain ugly. The first time you see them, you say to yourself, "If I ever have to use that, there's something wrong with my design." And most of the time you'd be right. Languages tend to be designed to fit the greatest common factor, which widens their appeal. Only problem is there's a lot of bad design in that GCF.

Enter Java nested classes. The first time I laid eyes on these, I couldn't imagine a single use for them that I wouldn't prefer to implement some other way.

Then I noticed something very unique about the way Java handles nested classes. Unless a nested class is declared as a static member of its enclosing class, an instance of the enclosing class must exist before the nested class can be instantiated. For example: EnclosingClass.NestedClass nestedInstance = enclosingInstance.new NestedClass();

Those of you familiar with the Builder Pattern may already be thinking what I was. That enclosing class looks a lot like a Builder with the nested class as its Product! It turns out that an enclosing class can indeed be used in this way. In some cases, it can even provide stronger encapsulation compared to a traditional Builder implementation. In this article we're going to find out how by exploring three examples of implementing run-time composition for a given Product.

A Self-Composing Product

The simplest approach is to let the Product build itself. You can achieve run-time composition by passing an inventory of parts to the constructor. The Product can then decide what parts to use based on availability and some internal rules. Suppose, for example, you want to build meals. A meal is composed of courses which can vary by type (e.g. steak versus fish) and number (e.g. 7 courses versus 3). Your Product interface might look something like this:

package enclosingclassasbuilder;
                                       public interface Meal {
                                       String nextCourse(); //consume the next course
                                       }
                                     

Next you need an inventory of parts. For brevity's sake let's just whip up something simple and obtuse:

package
                                       enclosingclassasbuilder;
                                       import java.util.ArrayList;
                                       public class Refrigerator {
                                       public ArrayList<String> inventory = new ArrayList<String>();
                                       }
                                     

Now you can implement the Meal interface as a self-composing Product like so:

package enclosingclassasbuilder;
                                       import java.util.ArrayList;
                                       public class SelfComposingMeal implements Meal {
                                       private ArrayList<String> courses = new ArrayList<String>();
                                       public SelfComposingMeal(Refrigerator fridge) {
                                       //rules for selecting entree
                                       if( fridge.inventory.contains("steak") ) {
                                       courses.add("steak");
                                       fridge.inventory.remove("steak");
                                       }
                                       else if( fridge.inventory.contains("fish") ) {
                                       courses.add("fish");
                                       fridge.inventory.remove("fish");
                                       }
                                       //rules for selecting side
                                       if( fridge.inventory.contains("potato") ) {
                                       courses.add("potato");
                                       fridge.inventory.remove("potato");
                                       }
                                       else if( fridge.inventory.contains("asparagus") ) {
                                       courses.add("asparagus");
                                       fridge.inventory.remove("asparagus");
                                       }
                                       //rules for selecting dessert
                                       if( fridge.inventory.contains("cobbler") ) {
                                       courses.add("cobbler");
                                       fridge.inventory.remove("cobbler");
                                       }
                                       else if( fridge.inventory.contains("cake") ) {
                                       courses.add("cake");
                                       fridge.inventory.remove("cake");
                                       }
                                       }
                                       
                                       public String nextCourse() {
                                       if( !courses.isEmpty() ) return courses.remove(0);
                                       return null;
                                       }
                                       }
                                     

While this implementation is very straightforward and easily extensible, it exhibits poor encapsulation. Passing an instance of Refrigerator to the constructor of SelfComposingMeal means that you've got a public or package public inventory floating around out there. Not only are you trusting your users to build their own Meals, you're giving them free access to your Refrigerator! What's to stop them from building a "lobsterfest" Meal that consumes all of your most expensive items in one sitting? Clearly, we need a better solution.

Using the Traditional Builder Pattern

You want to restrict access to the inventory of parts used to build Meals. You'd also like to discourage your users from building their own Meals. Implementing a traditional Builder is a fine way to accomplish both. Let's start with the interface:

package
                                       enclosingclassasbuilder;
                                       public interface Chef {
                                       Meal buildMeal();
                                       }
                                     

Hey, that was easy! Now let's put some meat on those bones. First we need a concrete Product for our concrete Builder to build:

package enclosingclassasbuilder;
                                       import java.util.ArrayList;
                                       public class ChefConstructedMeal implements Meal {
                                       public ArrayList<String> courses = new ArrayList<String>();
                                       public ChefConstructedMeal() {
                                       }
                                       
                                       public String nextCourse() {
                                       if( !courses.isEmpty() ) return courses.remove(0);
                                       return null;
                                       }
                                       }
                                     

Now we can implement our Builder:

package enclosingclassasbuilder;
                                       public class NonEnclosingChef implements Chef {
                                       private Refrigerator m_ChefsFridge = new Refrigerator();
                                       public NonEnclosingChef() {
                                       }
                                       public NonEnclosingChef(Refrigerator fridge) {
                                       while( !fridge.inventory.isEmpty() ) m_ChefsFridge.inventory.add( fridge.inventory.remove(0) );
                                       }
                                       public Meal buildMeal() {
                                       ChefConstructedMeal meal = new ChefConstructedMeal();
                                       //rules for building entree
                                       if( m_ChefsFridge.inventory.contains("steak") ) {
                                       meal.courses.add("steak");
                                       m_ChefsFridge.inventory.remove("steak");
                                       }
                                       else if( m_ChefsFridge.inventory.contains("fish") ) {
                                       meal.courses.add("fish");
                                       m_ChefsFridge.inventory.remove("fish");
                                       }
                                       //rules for building side
                                       if( m_ChefsFridge.inventory.contains("potato") ) {
                                       meal.courses.add("potato");
                                       m_ChefsFridge.inventory.remove("potato");
                                       }
                                       else if( m_ChefsFridge.inventory.contains("asparagus") ) {
                                       meal.courses.add("asparagus");
                                       m_ChefsFridge.inventory.remove("asparagus");
                                       }
                                       //rules for building dessert
                                       if( m_ChefsFridge.inventory.contains("cobbler") ) {
                                       meal.courses.add("cobbler");
                                       m_ChefsFridge.inventory.remove("cobbler");
                                       }
                                       else if( m_ChefsFridge.inventory.contains("cake") ) {
                                       meal.courses.add("cake");
                                       m_ChefsFridge.inventory.remove("cake");
                                       }
                                       return meal;
                                       }
                                       }
                                     

This is definitely an improvement over our previous implementation. Now we can keep our inventory private and control how Meals are constructed. What's that you say? We still don't completely control how Meals are constructed since the class itself is publicly available? That's a good point. Notice too how we've had to make our Meal's courses publicly available so that our Chef can add to them. It seems our encapsulation problem has migrated and now our users are free to alter the Meals they receive.

There's another problem. What if our Chef needs to build several different types of Meals (e.g. breakfast, TexMex, Happy) from the same inventory? And what if we need to allow for the possibility that our Chef will move to a new restaurant where they'll have to add even more Meals to their repertoire? Now you have to implement something like a Builder/Factory and your buildMeal() method will become hopelessly complex and difficult to maintain. Sure, you can split it up into lots of private "buildXMeal" methods, but at what point does your Chef morph into the dreaded God Object? Do you really want to find out? Me neither.

Implementing the Builder with an Enclosing Class

Now you'd like to restrict access to the courses of a Meal as well as the inventory of parts used to build them. You also want to put the compositional logic back into your Meals to ease the burden on your Builder. You are really high maintenance. Lucky for you, Java's handling of nested classes gives you the power to do both. Here's how:

1 2 Page 1
Page 1 of 2