Using State Guard in GUI

The State Guard pattern works very well with the patterns Builder, Chained Creator and Context Switcher but here we will demonstrate how it can be used all by itself in a graphical user interface to handle validation and transition to a valid state. The example is written in Java and uses Swing with the layout manager SpringLayout. The source code is hosted at GitHub.

Lets say we want to create this GUI:

The program starts by executing the class Main:
package nu.tengstrand.stateguard.guiexample;

import nu.tengstrand.stateguard.guiexample.person.Person;
import nu.tengstrand.stateguard.guiexample.person.PersonStateGuard;

import javax.swing.*;

public class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                final PersonStateGuard personStateGuard = new PersonStateGuard();

                new PersonFrame(personStateGuard, new SaveCommand() {
                    public void save() {
                        Person person = personStateGuard.asValidState();
                        new PopupFrame(person);
                   }
                });
            }
        });
    }
}
  • row 13:
    the class PersonStateGuard holds all the attributes used in the GUI.
  • row 15:
    references of PersonStateGuard and the interface SaveCommand is sent in to the constructor of PersonFrame:
package nu.tengstrand.stateguard.guiexample;

import nu.tengstrand.stateguard.Validatable;
import nu.tengstrand.stateguard.guiexample.person.PersonStateGuard;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ResourceBundle;

public class PersonFrame extends JFrame {
    static ResourceBundle resourceBundle = ResourceBundle.getBundle("validationMessages");

    public PersonFrame(final PersonStateGuard person, final SaveCommand saveCommand) {
        setTitle("State Guard example - by Joakim Tengstrand");
        setPreferredSize(new Dimension(450, 190));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Container contentPane = getContentPane();
        SpringLayout layout = new SpringLayout();
        contentPane.setLayout(layout);

        // Name
        JLabel nameLabel = new JLabel("Name: ");
        JTextField nameTextField = new JTextField("", 15);
        JLabel nameError = new JLabel();
        nameError.setForeground(Color.RED);
        contentPane.add(nameLabel);
        contentPane.add(nameTextField);
        contentPane.add(nameError);

        // Age
        JLabel ageLabel = new JLabel("Age: ");
        JTextField ageTextField = new JTextField("", 5);
        JLabel ageError = new JLabel();
        ageError.setForeground(Color.RED);
        contentPane.add(ageLabel);
        contentPane.add(ageTextField);
        contentPane.add(ageError);

        // Country
        JLabel countryLabel = new JLabel("Country: ");
        JTextField countryTextField = new JTextField("", 10);
        JLabel countryError = new JLabel();
        countryError.setForeground(Color.RED);
        contentPane.add(countryLabel);
        contentPane.add(countryTextField);
        contentPane.add(countryError);

        // Save button
        final JButton saveButton = new JButton("Save");
        saveButton.setEnabled(false);
        saveButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveCommand.save();
            }
        });
        contentPane.add(saveButton);

        // Validation explanation
        JLabel validationErrorExplanationLabel = new JLabel("* = Mandatory field");
        contentPane.add(validationErrorExplanationLabel);

        connectTextFieldToModel(person.name(), nameTextField, nameError, person, saveButton);
        connectTextFieldToModel(person.age(), ageTextField, ageError, person, saveButton);
        connectTextFieldToModel(person.country(), countryTextField, countryError, person, saveButton);

        // Spring layout constraints
        layout.putConstraint(SpringLayout.WEST, nameLabel, 5, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, nameLabel, 5, SpringLayout.NORTH, contentPane);
        layout.putConstraint(SpringLayout.WEST, nameTextField, 80, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, nameTextField, 5, SpringLayout.NORTH, contentPane);
        layout.putConstraint(SpringLayout.WEST, nameError, 20, SpringLayout.EAST, nameTextField);
        layout.putConstraint(SpringLayout.NORTH, nameError, 5, SpringLayout.NORTH, contentPane);

        layout.putConstraint(SpringLayout.WEST, ageLabel, 5, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, ageLabel, 25, SpringLayout.NORTH, nameTextField);
        layout.putConstraint(SpringLayout.WEST, ageTextField, 80, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, ageTextField, 25, SpringLayout.NORTH, nameTextField);
        layout.putConstraint(SpringLayout.WEST, ageError, 20, SpringLayout.EAST, ageTextField);
        layout.putConstraint(SpringLayout.NORTH, ageError, 25, SpringLayout.NORTH, nameTextField);

        layout.putConstraint(SpringLayout.WEST, countryLabel, 5, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, countryLabel, 25, SpringLayout.NORTH, ageTextField);
        layout.putConstraint(SpringLayout.WEST, countryTextField, 80, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, countryTextField, 25, SpringLayout.NORTH, ageTextField);
        layout.putConstraint(SpringLayout.WEST, countryError, 20, SpringLayout.EAST, countryTextField);
        layout.putConstraint(SpringLayout.NORTH, countryError, 25, SpringLayout.NORTH, ageTextField);

        layout.putConstraint(SpringLayout.WEST, validationErrorExplanationLabel, 80, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, validationErrorExplanationLabel, 30, SpringLayout.NORTH, countryLabel);

        layout.putConstraint(SpringLayout.WEST, saveButton, 80, SpringLayout.WEST, contentPane);
        layout.putConstraint(SpringLayout.NORTH, saveButton, 30, SpringLayout.NORTH, validationErrorExplanationLabel);

        pack();
        setVisible(true);
    }

    private void connectTextFieldToModel(final ValidatableStringValue validatableStringValue, JTextField textField, final JLabel error, final Validatable person, final JButton saveButton) {
        error.setText(validatableStringValue.validationMessages().firstMessage(resourceBundle));

        textField.getDocument().addDocumentListener(new UpdateTextListener() {
            public void setText(String text) {
                validatableStringValue.setValue(text);
                error.setText(validatableStringValue.validationMessages().firstMessage(resourceBundle));
                saveButton.setEnabled(person.isValid());
            }
        });
    }
}
  • row 16-63, 69-98:
    GUI setup code
  • row 54:
    Callback to Main via the SaveCommand interface.
  • row 65-67:
    When a text field is edited, this will happen:
    • the corresponding attribute (name, age, country) in PersonStateGuard is updated.
    • if the text field does not validate, the validation message is shown (asterisk if empty).
    • the save button is enabled/disabled depending on if the PesonStateGuard instance is valid (line 108).
Lets demonstrate this with a couple of pictures:

The asterisk shows the mandatory fields. When the Name is filled in, the asterisk goes away. This is handled by the class NameStateGuard (the property mandatory.field at row 9 is stored in the property file validationMessages):
package nu.tengstrand.stateguard.guiexample.person;

import nu.tengstrand.stateguard.StateGuard;
import nu.tengstrand.stateguard.guiexample.ValidatableStringValue;
import nu.tengstrand.stateguard.validator.NonEmptyString;

public class NameStateGuard extends StateGuard implements ValidatableStringValue {
    private NonEmptyString name = NonEmptyString.attributeName("name")
                                  .messageKey("mandatory.field");

    public NameStateGuard() {
        addValidator(name);
    }

    public void setValue(String value) {
        name.setValue(value);
    }

    @Override
    protected Name createValidState() {
        return new Name(name.value());
    }
}

When an invalid integer is typed, a validation error is shown. This is handled by the class AgeValidator in AgeStateGuard:
package nu.tengstrand.stateguard.guiexample.person;

import nu.tengstrand.stateguard.StateGuard;
import nu.tengstrand.stateguard.ValidationMessages;
import nu.tengstrand.stateguard.guiexample.ValidatableStringValue;
import nu.tengstrand.stateguard.validator.IntegerValidator;

public class AgeStateGuard extends StateGuard implements ValidatableStringValue {
    private AgeValidator age = new AgeValidator();

    private static final int MIN_AGE = 0;
    private static final int MAX_AGE = 150;

    public AgeStateGuard() {
        addValidator(age);
    }

    public void setValue(String value) {
        age.setValue(value);
    }

    @Override
    protected Age createValidState() {
        return new Age(age.value());
    }


    private static class AgeValidator extends IntegerValidator {
        AgeValidator() {
            super("age");
        }

        @Override
        public boolean isValid() {
            return super.isValid() && value() >= MIN_AGE && value() <= MAX_AGE;
        }

        @Override
        public ValidationMessages validationMessages() {
            if (isValid()) {
                return ValidationMessages.withoutMessage();
            }
            if (stringValue() == null || stringValue().length() == 0) {
                return ValidationMessages.message("*");
            }
            return ValidationMessages.message("Enter a valid age");
        }
    }
}
The AgeValidator also checks if the age is within the range 0 to 150 (line 35).
The validation of Country is handled by the class CountryValidator in CountryStateGuard. The only valid countries are Sweden and Norway (case insensitive).
Now, when all fields are filled in correct, the save button is enabled.
When the save button is clicked we now know that the instance of PersonStateGuard is valid so we can safely let it create a valid instance of Person at line 17 in Main. The resulting instance of Person is shown in the popup window PopupFrame.

Best Regards,
Joakim Tengstrand


The State Guard Pattern

A common task is to write code that handles type conversions and validations, often in combination with conversion of data structures or transition from a mutable to an immutable state. One example is where you need to read data from a file that needs to be validated, type converted and packaged into a new class or class structure.

Let's take another example. You need to create a GUI that registers users and you want to store them in the class Person with the attributes ssn, name and country. Assume that you want to handle these attributes with the value objects SSN, PersonName and Country. The three attributes are handled as strings in the GUI and you also want to display error messages if any of them can't be translated to the corresponding value object.

The need here is to transform a structure of strings, and package it as an instance of Person. Before that can happen, the validations has to be performed on its internal representation, and any error messages should be displayed in the GUI. This pattern presents a solution that allows a dedicated class StateGuard to be responsible for type conversion, validations and state transitions.

In our example this is handled by the class PersonStateGuard and its attributes by the classes SSNStateGuard, PersonNameStateGuard and CountryStateGuard. This solution provides separation of concerns, the ability to build reusable validation components and ensures that all validations have passed before the transition to a valid state can happen. Here comes a more detailed description of the State Guard pattern.

Let's call the validated class with valid state X. The responsibility for validation and creation of instances of X belongs in this pattern to the class XStateGuard that inherits from StateGuard.
XStateGuard gives the advantage that class X can be immutable or it can be switchable from mutable to immutable in a controlled manner.

With the State Guard patten you can:
  • Ensure that all validations have been executed before the instance of X is created, example: X.create(params...).asValidState(), where create returns an instance of XStateGuard. If you want to allow creation of instances of X directly, you need to make the constructor of X public and add a check that throws an exception if new XStateGuard (params...).isValid () returns false.
  • Create State Guard components that can be used as building blocks for other state guard classes. The classes NotNull and NonEmptyString in package nu.tengstrand.stateguard.validator are examples of such validators.
  • Get access to the instance of XStateGuard with methods that faciliates validation and transition to valid state:
    • isValid(): returns true if in a valid state.
    • validationMessages(): returns any validation errors.
    • asValidState(): returns an instance of X if XStateGuard is in a valid state, otherwise an exception is thrown.
Example Main.java:
package nu.tengstrand.stateguard.example;

import nu.tengstrand.stateguard.example.book.Book;
import nu.tengstrand.stateguard.example.book.BookStateGuard;
import nu.tengstrand.stateguard.example.book.attributes.BookBinding;
import nu.tengstrand.stateguard.example.book.BookBuilder;
import nu.tengstrand.stateguard.example.book.BookCreator;

import java.util.ResourceBundle;

public class Main {
    static ResourceBundle resourceBundle = ResourceBundle.getBundle("validationMessages");

    public static void main(String[] args) {
        System.out.println("----- Build: Missing attributes ------");
        BookBuilder bookBuilder = Book.build().title("My Book");
        printValidationMessages(bookBuilder);

        System.out.println("\n----- Build: With binding + pages ------");
        bookBuilder.binding(BookBinding.PAPERBACK).pages(50);
        printValidationMessages(bookBuilder);
        Book bookWithMissingBinding = bookBuilder.asValidState();
        System.out.println("Book: " + bookWithMissingBinding);

        System.out.println("\n----- Create: Empty title ------");
        BookCreator bookCreatorWithEmptyTitle = Book.create().title("").paperback().pages(100);
        printValidationMessages(bookCreatorWithEmptyTitle);

        System.out.println("\n----- Create: Thick book ------");
        BookCreator thickBookCreator = Book.create().title("Thick book").paperback().pages(3000);
        printValidationMessages(thickBookCreator);

        System.out.println("\n----- Create: Valid book ------");
        BookCreator bookCreator = Book.create().title("The book").paperback().pages(200);
        printValidationMessages(bookCreator);
        Book book = bookCreator.asValidState();
        System.out.println("Book: " + book);
    }

    private static void printValidationMessages(BookStateGuard stateGuard) {
        System.out.println("Valid=" + stateGuard.isValid());
        for (String formattedMessage : stateGuard.validationMessages().formattedMessages(resourceBundle)) {
            System.out.println(formattedMessage);
        }
    }
}
validationMessages.properties:
missingvalue=Missing value, please enter a value for ''{0}''
book.pages=The attribute 'pages' must be greater than zero, but was {0}

This will generate the following output:
----- Build: Missing attributes ------
Valid=false
Attribute 'pages' must be greater than zero, but was 0
Attribute 'binding' can not be null

----- Build: With binding + pages ------
Valid=true
Book: Book{title='My Book', binding=PAPERBACK, pages=50}

----- Create: Empty title ------
Valid=false
Missing value, please enter a value for 'title'

----- Create: Thick book ------
Valid=true

----- Create: Valid book ------
Valid=true
Book: Book{title='The book', binding=PAPERBACK, pages=200}

Here we use a resourceBundle which replaces the default validation messages. If formattedMessages() at line 42 is called without arguments, the default messages are used.

The example makes use of the patterns builder (line 16) and chained creator (line 26, 30 and 34). This is not required but increase the readability of the code. The source is hosted at GitHub and has the following structure:


The package example.book contains all classes that handles a Book. In the package book.attributes you can see that all attributes also have a corresponding state guard. In package book the state guard classes for Book are handled by BookStateGuard, BookBuilder and BookCreator. Other classes directly under nu.tengstrand.stateguard are all part of the framework State Guard.

We have put the shared code for BookBuilder and BookCreator in the class BookStateGuard as we in this example can create instances of Book by using both Book.create() and Book.build():
package nu.tengstrand.stateguard.example.book;

import nu.tengstrand.stateguard.StateGuard;
import nu.tengstrand.stateguard.example.book.attributes.BookBindingStateGuard;
import nu.tengstrand.stateguard.example.book.attributes.BookPagesStateGuard;
import nu.tengstrand.stateguard.example.book.attributes.BookTitleStateGuard;

public abstract class BookStateGuard extends StateGuard<Book> {
    protected BookTitleStateGuard title = new BookTitleStateGuard();
    protected BookBindingStateGuard binding = new BookBindingStateGuard();
    protected BookPagesStateGuard pages = new BookPagesStateGuard();

    public BookStateGuard() {
        addValidators(title, binding, pages);
    }

    @Override
    protected Book createValidState() {
        return new Book(title.asValidState(), binding.asValidState(), pages.asValidState());
    }
}

This is an example of a state guard with more than one attribute, classes with only one attribute follows the same pattern:
  • row 8:
    inherits from the class StateGuard and indicates that there is a Book we want to create a valid instance of.
  • row 9-11:
    the class attributes in the form of three state guard which all have a counterpart in Book
  • row 14:
    register the attributes to be validated. You can also add other validators, the only requirement is that they implement the interface Validatable.
  • rad 18:
    returns a Book which represents our valid state. Set the method to protected so it can only be used by StateGuard.
BookBuilder:
package nu.tengstrand.stateguard.example.book;

import nu.tengstrand.stateguard.example.book.attributes.BookBinding;

public class BookBuilder extends BookStateGuard {
    BookBuilder() {
    }

    public BookBuilder title(String title) {
        this.title.setTitle(title);
        return this;
    }

    public BookBuilder binding(BookBinding binding) {
        this.binding.setBinding(binding);
        return this;
    }

    public BookBuilder pages(int pages) {
        this.pages.setPages(pages);
        return this;
    }
}

This StateGuard implements the pattern Builder:
  • row 5:
    inherits from the class BookStateGuard.
  • row 6:
    empty constructor with the visibility package-private, to force the usage of Book.build().
  • row 9, 14 and 19:
    set attributes and returns this.
BookCreator:
package nu.tengstrand.stateguard.example.book;

import nu.tengstrand.stateguard.example.book.attributes.BookBinding;

public class BookCreator extends BookStateGuard {
    BookCreator() {
    }

    public class Title {
        public Binding title(String title) {
            BookCreator.this.title.setTitle(title);
            return new Binding();
        }
    }

    public class Binding {
        public Pages paperback() {
            BookCreator.this.binding.setBinding(BookBinding.PAPERBACK);
            return new Pages();
        }
        public Pages hardback() {
            BookCreator.this.binding.setBinding(BookBinding.HARDBACK);
            return new Pages();
        }
    }

    public class Pages {
        public BookCreator pages(int pages) {
            BookCreator.this.pages.setPages(pages);
            return BookCreator.this;
        }
    }
}
Implements a chained creator for the Book class:
  • row 5:
    inherits from the class BookStateGuard.
  • row 6:
    empty constructor with visibility package-private, to force the usage of Book.create().
  • row 9-14:
    the first argument and main entrance of the chained constructor, returns the next argument Binding.
  • row 16-25:
    the middle argument in the constructor, returns the next argument Pages.
  • row 27-32:
    the last argument in the constructor, returns an instance of the surrounding class BookCreator.
The three attributes that are included in BookStateGuard (title, binding and pages) inherits all from StateGuard. These classes follow the same pattern so lets have a look at one of them, BookTitleStateGuard:
package nu.tengstrand.stateguard.example.book.attributes;

import nu.tengstrand.stateguard.StateGuard;
import nu.tengstrand.stateguard.validator.NonEmptyString;

public class BookTitleStateGuard extends StateGuard<BookTitle> {
    private NonEmptyString title = NonEmptyString.attributeName("title").messageKey("missingvalue");

    public BookTitleStateGuard() {
        addValidator(title);
    }

    public void setTitle(String title) {
        this.title.withValue(title);
    }

    @Override
    protected BookTitle createValidState() {
        return new BookTitle(title.value());
    }
}
  • row 6:
    inherits from the class StateGuard and indicates that it is the class BookTitle we want to protect.
  • row 7:
    validator for the attribute title where NonEmptyString states that the value may not be null or empty string. The first argument "title" will appear in the error message. The second argument "messageKey" is optional and replaces the default message for the component NonEmptyString with the message missingvalue in the file validationMessages.properties, which in this case is: Missing value, please enter a value for''{0 }''.
  • row 10:
    the validator title is registered in the constructor.
  • row 13:
    is used by BookBuilder and BookCreator.
  • row 18:
    returns a BookTitle. The fact that we inherit from StateGuard, has registered title at line 10 and set this method to protected will guarantee that title is validated before this method is called with the result that we can be sure that title is never null or empty string.
With these words I’m wishing you good luck with this pattern!

Best Regards,
Joakim Tengstrand


The Chained Creator Pattern

Sometimes a little extra care around a certain part of the code can really pay off. In one case, you want to achieve higher quality by reducing the number of errors and make the code easier to work with. In another case, the code is used in many places so it will pay off if you can simplify the API. If you build a framework that is used by a lot of people, then a small improvement makes a huge difference.

The purpose of this pattern is to simplify a very common occurrence, the creation of class instances, by making the code cleaner and more readable. It can be used to create ordinary class instances and also replace long parameter lists (if you can't avoid them) with a "parameter class".

Most of the deficiencies this pattern addresses can also be resolved by named parameters and default arguments, supported by some languages. However, you can still benefit by using this pattern even if your language has support for these two constructions.

The only real downside of the pattern is that you need to add some boilerplate code in the Creator.

The upsides are more numerous:
  • Lets the code communicate What instead of How, for example Car.create().defaults() instead of new Car().
  • View the name of the arguments, which makes the code more readable.
  • Support for default values anywhere in the parameter list, not just at the end.
  • Make use of enumerated parameters more readable with less boiler-plate code.
  • Change the order of parameters with same type without the risk of introducing errors.
  • One constructor with support for different paths, no need for a set of constructors.
  • Guarantees that all attributes in the object are set.
Let’s look at some examples (the source is hosted at GitHub):
package nu.tengstrand.chainedcreator;

import nu.tengstrand.chainedcreator.book.*;
import nu.tengstrand.chainedcreator.car.Car;

public class Main {
    /**
     * This is an example of the pattern Chained Creator.
     *
     * Author: Joakim Tengstrand
     */
    public static void main(String[] args) {
        bookExampleUsingPrimitiveTypesNotUsingChainedCreator();
        bookExampleUsingValueObjectsNotUsingChainedCreator();

        bookExampleNoDefauls();
        bookExampleWithAllDefaults();
        bookExampleWithSomeDefaults();

        bookExampleUsingAMixOfPrimitivesAndValueObjects();
        carExampleSendingInPrimitiveTypesInClassWithPrimitiveAttributes();
    }

    // Example 1 - regular constructor
    private static void bookExampleUsingPrimitiveTypesNotUsingChainedCreator() {
        Book book = new Book("Clean Code", BookBinding.PAPERBACK, "Robert C Martin", 
                431, 660);
        printExample(1, book);
    }

    // Example 2 - regular constructor
    private static void bookExampleUsingValueObjectsNotUsingChainedCreator() {
        Book book = new Book(new BookTitle("The Pragmatic Programmer"),
                BookBinding.PAPERBACK,
                new BookAuthor("Andrew Hunt, David Thomas"),
                new BookNumberOfPages(352),
                new BookWeightInGrams(540));
        printExample(2, book);
    }

    // Example 3
    private static void bookExampleNoDefauls() {
        Book book = Book.create().title("Test Driven").bindingPaperback()
                .author("Lasse Koskela").numberOfPages(544).weighInGrams(1180);
        printExample(3, book);
    }

    // Example 4
    private static void bookExampleWithAllDefaults() {
        Book book = Book.create().defaults();
        printExample(4, book);
    }

    // Example 5
    private static void bookExampleWithSomeDefaults() {
        // Using default values for 'binding', 'author' and 'weightInGrams'.
        Book book = Book.create().title("Thin book").defaultBinding()
                .unknownAuthor().numberOfPages(125).defaultWeightInGrams();
        printExample(5, book);
    }

    // Example 6
    private static void bookExampleUsingAMixOfPrimitivesAndValueObjects() {
        Book book = Book.create()
                .title(new BookTitle("Programming in Scala, 2nd Edition"))
                .bindingPaperback()
                .author("Martin Odersky, Lex Spoon, Bill Venners")
                .numberOfPages(883)
                .weighInGrams(1134);
        printExample(6, book);
    }

    // Example 7
    private static void carExampleSendingInPrimitiveTypesInClassWithPrimitiveAttributes() {
        Car car = Car.create().name("Lamborghini").color("Red").length(458);
        printExample(7, car);
    }

    private static void printExample(int example, Object instance) {
        System.out.println(example + ". " + instance);
    }
}

Output:
1. Book{title='Clean Code', author='Robert C Martin', numberOfPages=431, weighInGrams=660}
2. Book{title='The Pragmatic Programmer', author='Andrew Hunt, David Thomas', numberOfPages=352, weighInGrams=540}
3. Book{title='Test Driven', author='Lasse Koskela', numberOfPages=544, weighInGrams=1180}
4. Book{title='UNKNOWN TITLE', author='UNKNOWN AUTHOR', numberOfPages=2, weighInGrams=200}
5. Book{title='Thin book', author='UNKNOWN AUTHOR', numberOfPages=125, weighInGrams=200}
6. Book{title='Programming in Scala, 2nd Edition', author='Martin Odersky, Lex Spoon, Bill Venners', numberOfPages=883, weighInGrams=1134}
7. Car{name='Lamborghini', color='Red', length=458}

Example 1 and 2 shows regular constructors. Example 3 to 7 shows usage of this pattern, Chained Creator.

Example 1

The problem here is that it's not clear what the arguments 431, 660 (line 27) is representing.

Example 2

There is no doubt what the different parameters represents, but the argument list is not very clean.

Example 3

The syntax is very readable and clean, using the pattern Chained Creator.

Example 4

Example of how to set all the class' attributes to default values.

Example 5

Example of how to set some of the arguments to default values.

Example 6

In this example we use bindingPaperback() instead of binding(BookBinding.PAPERBACK) to improve the user experience of the API.

Example 7

A chained creator with only primitive data types used by the class Car consisting solely of primitives. Here we take advantage of Java's ability to reference the enclosing class (if implemented in a languages without support for this, you would need to provide a reference of Car to your parameter classes):

package nu.tengstrand.chainedcreator.car;

public class Car {
    private String name;
    private String color;
    private int length;

    // Package-private access level
    Car() {
    }

    // Creates an instance of a Car
    public static Name create() {
        return new Car().new Name();
    }

    // Example of not using a separate creator class, and take advantage of non-static
    // inner classes in Java who can can access attributes of the enclosing class.
    //
    // Parameter chain. If you have a lot of attributes, this is an example of a more compact format.
    public class Name { public Color name(String name) { Car.this.name = name; return new Color(); }}
    public class Color { public Length color(String color) { Car.this.color = color; return new Length(); }}
    public class Length { public Car length(int length) { Car.this.length = length; return Car.this; }}

    @Override
    public String toString() {
        return "Car{" + "name='" + name + "', color='" + color + "', length=" + length + '}';
    }
}

The trick here is to set the constructor to package-private (line 9) and add the public static method create(), at line 13. If your instinctive reaction is to not allow Car to create instances of itself, keep in mind that the class already has this responsibility by providing the ability to write new Car(). With that said, we don't want to introduce a CarFactory which would also make the code less readable.

In example 3 to 6 we are introducing the class Book:
package nu.tengstrand.chainedcreator.book;

/**
 * This is an example how you can use the pattern Chained Creator
 * with a mix of value objects and primitive data types.
 */
public class Book {
    BookTitle title;
    BookBinding binding;
    BookAuthor author;
    BookNumberOfPages numberOfPages;
    BookWeightInGrams grams;

    BookCreator creator = new BookCreator(this);

    // Package-private access level
    Book() {
    }

    // Example of a regular constructor, used in Example 1. 
    public Book(String title, BookBinding binding, String author, int numberOfPages, int grams) {
        this(new BookTitle(title), binding, new BookAuthor(author), new BookNumberOfPages(numberOfPages), new BookWeightInGrams(grams));
    }
    
    // Example of a regular constructor, used in Example 2. 
    public Book(BookTitle title, BookBinding binding, BookAuthor author, BookNumberOfPages numberOfPages, BookWeightInGrams grams) {
        this.title = title;
        this.binding = binding;
        this.author = author;
        this.numberOfPages = numberOfPages;
        this.grams = grams;
    }

    // Create a book by using the pattern Chained Creator.
    public static BookCreator.Title create() {
        return new Book().creator.create();
    }

    @Override
    public String toString() {
        return "Book{title='" + title + "', author='" + author + "', numberOfPages=" + numberOfPages + ", weighInGrams=" + grams + "}";
    }
}

To use the pattern Chained Constructor you should not implement the constructors at line 20 to 32, they are added to have something to compare with (used in example 1 and 2). In this example, the class Book contains only value object (line 8 to 12), but you are free to choose how your class is represented internally.

When you have added your class variables you also need to add the BookCreator instance variable as package-private (line 14) and a public static create() method (line 35).

The next thing to do is to implement the class BookCreator:
package nu.tengstrand.chainedcreator.book;

public class BookCreator {
    private Book book;

    public BookCreator(Book book) {
        this.book = book;
    }

    /**
     * Returns the first argument in the chain.
     */
    public Title create() {
        return new Title();
    }

    public class Title {
        public Binding title(String title) {
            return title(new BookTitle(title));
        }
        public Binding title(BookTitle title) {
            book.title = title;
            return new Binding();
        }
        public Book defaults() {
            title("UNKNOWN TITLE");
            new Binding().defaultBinding();
            new Author().unknownAuthor();
            book.numberOfPages = new BookNumberOfPages(2);
            new WeightInGrams().defaultWeightInGrams();
            return book;
        }
    }

    public class Binding {
        public Author binding(BookBinding binding) {
            book.binding = binding;
            return new Author();
        }
        public Author defaultBinding() {
            return bindingPaperback();
        }
        // An example of how to make the constructor chain more readable,
        // bindingPaperback() instead of binding(BookBinding.PAPERBACK).
        public Author bindingPaperback() {
            return binding(BookBinding.PAPERBACK);
        }
        public Author bindingHardback() {
            return binding(BookBinding.HARDBACK);
        }
    }

    public class Author {
        public NumberOfPages author(String author) {
            book.author = new BookAuthor(author);
            return new NumberOfPages();
        }

        public NumberOfPages unknownAuthor() {
            return author("UNKNOWN AUTHOR");
        }
    }

    public class NumberOfPages {
        public WeightInGrams numberOfPages(int numberOfPages) {
            book.numberOfPages = new BookNumberOfPages(numberOfPages);
            return new WeightInGrams();
        }
    }

    public class WeightInGrams {
        public Book weighInGrams(int grams) {
            book.grams = new BookWeightInGrams(grams);
            return book;
        }

        public Book defaultWeightInGrams() {
            return weighInGrams(BookWeightInGrams.DEFAULT_WEIGHT);
        }
    }
}

Start with adding the instance variable to the class Book (line 4) and the constructor (line 6). My experience is that it is easiest to start with the last argument in the list, so let's add the inner class WeightInGrams (line 71). We want this parameter to support sending in weight in grams as an int and we also want it to handle setting a default value. We therefore add the two methods at line 72 and 77 and let them return a Book.

Continue with the second last parameter, by adding the class NumberOfPages (line 64). We decide not to handle default values for this parameter. Add the method numberOfPages(int) and let it return an instance to the next class in the constructor chain WeightInGrams.

Continue with all other parameters, Author, Binding and Title. Finish with creating the create() method (line 13). Now we are done!

With this pattern you need to do some extra work with the code that handles the creation of a class, but in return you get more control of every parameter in the parameter list.

Best regards,
Joakim Tengstrand


The Context Switcher Pattern

What is your relationship to encapsulation? Do you belong to the group who always start with setting all of the class's internal attributes to private, but who often end up needing to add getters because they couldn’t see a better solution? Or perhaps you belong to the group that has given up on encapsulation and who let their IDEs generate all the getters when the class is created? Maybe you belong to the third group who do everything in their power to ensure they maintain encapsulation; even going so far as to use instrumentation “magic” or code generation (or possibly both) to avoid adding those unwanted getters.

Most programmers agree that encapsulation is a good thing. It allows you to protect internal representation and control which methods are exposed to the outside world. If, for example, you have created the class Car, you have created something that you can relate to in a natural way which will make the code more readable and understandable, easier to work with and become a natural place to put all behavior that is related to the concept car. All related code will be gathered in one place which is an important principle in programming and helps to prevent unwanted dependencies that otherwise can easily occur when behavior is spread out in various places in the code.

Have you ever wondered in what situations you are forced to violate encapsulation? Let us say that you create the class Car and add a couple of nice methods that are needed in the business layer, for example isBig(). You also need to add the getter getName() but you can't see what harm that could cause. For the first it returns an immutable type (String) that can't change the inner state of Car via that reference. And second, you don’t think anyone will do anything else than reading it. What can happen is that code outside Car operates on name in other ways than just reading it. If that happens, it is behaviour, and moving behaviour out from the object is a bad thing and is not what object orientation is all about. Moving code outside the car can also lead to code duplication which is not what we want to happen.

You think you have met those two criteria and it makes you feel both happy and proud. All other attributes, such as primary key to the car table in the database, are still set as private which is a good thing since these are details that the business layer does not care about. But it happens that someone asks you to add an export function for an old banking system. The file that is exported should follow a text format with fixed positions where every position holds an attribute of a car.

This is the moment where it happens - you are forced to violate encapsulation! Why do you need to do that? Because you have switched context. In the new context, we are suddenly not at all interested in the methods used in the business layer, such as isBig(), but on the other hand have urgent needs to access the internal class representation. We swallow our pride and add a couple of getters who we then use in our class CarBankExporter. We have now introduced the risk that behaviour will move outside Car and at the same time managed to pollute the cars API. Does this feels familiar? The next question is, is there any good solution to this problem?

What if we could create the instance car of the type Car and then context-switch this instance to another class instance with preserved internal representation, but with access to a new set of methods, tailor-made for our new context (export cars to a file)? Or if we could create an instance of the new "handle export" class and then context-switch to the version of Car to be used in the business layer. This helps us maintain the object oriented approach where we move behavior into the object rather than as in the case CarBankExporter where we did the opposite by moving the object to the behavior.

The latter is an example of procedural programming, and corresponds to calling a function with a Car struct in C. The solution to our problems is called context switching and is handled by the pattern Context Switcher.

The best way to explain something is often by example. Assume that we have our class Car, which is used in our business layer, and that we both need to export cars to file and store cars in a database. Let's take a look how the code for such a scenario could look like in Java (the source is hosted at GitHub), class Main:
package nu.tengstrand.contextswitcher;

import nu.tengstrand.contextswitcher.car.CarFactory;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

public class Main {

    /**
     * This is an example of the pattern Context Switcher.
     *
     * Author: Joakim Tengstrand, august 2011.
     */
    public static void main(String[] args) {
        // 1. Create an instance of Car and run some business logic.
        //    Only the method isBig(), that think cars >= 400 cm are big,
        //    is exposed from this context.
        Car car = CarFactory.createCar(479, "Volvo");
        System.out.println("The car: " + car);
        printIsBig(car);

        // 2. Switch context to "car in database" and save to database.
        //    Only the method save() is exposed from this context.
        DbPersister dbPersister = new DbPersister();
        CarInDb carInDb = car.asCarInDb(dbPersister);
        carInDb.save();

        // 3. Change context to "as row in file" and append to file.
        //    Only the method appendToFile() is exposed from this context.
        FileWriter fileWriter = new FileWriter("Car.txt");
        CarAsRowInFile carToBeStoredInFile = carInDb.asRowInFile();
        carToBeStoredInFile.appendToFile(fileWriter);

        // 4. Read a car from file and change context to "business layer".
        String rowInFile = new FileReader("Car.txt").readNextRowFromFile();
        CarAsRowInFile carFromFile = CarFactory.createCarAsRowInFile(rowInFile);
        System.out.println("Row in file context: " + carFromFile);
        Car businessLayerCarFromFile = carFromFile.asCar();
        System.out.println("Business layer context: " + businessLayerCarFromFile);
        printIsBig(businessLayerCarFromFile);
    }

    private static void printIsBig(Car car) {
        String not = car.isBig() ? "" : "not ";
        System.out.println("This car is " + not + "big!");
    }
}
The output looks like this:
The car: Car{lengthInCentimeters=479, name=Volvo}
This car is big!
'CarInDb{lengthInCentimeters=479, name=Volvo, primaryKey=12}' was saved to the database!
'0479,Volvo' was appended to file 'Car.txt'
Row in file context: CarAsRowInFile{"0384,Fiat"}
Business layer context: Car{lengthInCentimeters=384, name=Fiat}
This car is not big!
Our three classes now expose only the methods that are meaningful depending on what context they are in and we haven’t violated encapsulation. VoilĂ ! Problem solved!

How to use the pattern

For this pattern to be meaningful, it should be applied to an object-oriented language. Java is used in this example but any other object oriented language will do. It should also have support for the keywords private (or similar) to ensure encapsulation.

I have tried to keep the sample code, available for download here, as simple and readable as possible. It can sometimes miss validations, annotations as @Override and the like. We will concentrate on the class Car. The other two representations CarInDb and CarAsRowInFile is created using the same pattern.

A note about the example. Using a factory with static methods, works well in certain situations. However, in larger enterprise systems, it is likely you will want to use dependency injection to provide the code with references to factories or repositories which I will write about in a later blog post.

You should find the following packages and classes:


The UML diagram looks like this:


Start by creating the class CarInternals:
package nu.tengstrand.contextswitcher.car;

/**
 * Put the internal representation of a car, that is shared between
 * different representations, in this class!
 */
public class CarInternals {
    // We don't want to lose track of the PK when switching context
    // so we need to put it here together with the other shared attributes.
    public Integer primaryKey;
    public int lengthInCentimeters;
    public String name;

    public CarInternals(int lengthInCentimeters, String name) {
        if (lengthInCentimeters < 0 || lengthInCentimeters > 999) {
            throw new IllegalArgumentException("Illegal length: " + lengthInCentimeters);
        }
        this.lengthInCentimeters = lengthInCentimeters;
        this.name = name;
    }

    public String toString() {
        return "lengthInCentimeters=" + lengthInCentimeters + ", name=" + name;
    }
}
This is where you store all the attributes shared by the three different car representations. Remember to set them as public. It may feel a bit strange, since our goal is to protect our internal representation, but stay cool the internal representation will not be exposed!
Create an ordinary public constructor in a way that you would, if it was part of the class Car without using this pattern. Add the method toString where you list the attributes.

Create the class CarContextSwitcher:
package nu.tengstrand.contextswitcher.car;

import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

import java.util.HashMap;
import java.util.Map;

/**
 * This class is responsible for switching between cars that are tailor-made for a specific context.
 */
public class CarContextSwitcher {
    private final CarInternals internals;

    // Context swithable car representations
    private Car car;
    private CarAsRowInFile carAsRowInFile;
    private Map<DbPersister,CarInDb> carInDbs = new HashMap<DbPersister,CarInDb>();

    public CarContextSwitcher(CarInternals carInternals) {
        internals = carInternals;
    }

    public Car asCar() {
        if (car == null) {
            car = new Car(internals, this);
        }
        return car;
    }

    /**
     * The implementation of DbPersister may vary as we have to consider.
     */
    public CarInDb asCarInDb(DbPersister dbPersister) {
        if (carInDbs.containsKey(dbPersister)) {
            return carInDbs.get(dbPersister);
        }
        CarInDb carInDb = new CarInDb(internals, this, dbPersister);
        carInDbs.put(dbPersister, carInDb);

        return carInDb;
    }

    public CarAsRowInFile asRowInFile() {
        if (carAsRowInFile == null) {
            carAsRowInFile = new CarAsRowInFile(internals, this);
        }
        return carAsRowInFile;
    }

    public String toString() {
        return "CarContextSwitcher{internals=" + internals + "}";
    }
}

Add the attribute internals and make sure it is set to private final. To set it as final will ensure that it is assigned by the constructor and will not be changed. Add the attributes car, carInDb and carAsRowInFile. In the case carInDb we send in dbPersister which can vary, so we need to handle that case using a Map. Add the constructor and assign internals. Add method asCar where you return a Car.

Continue with the class Car:
package nu.tengstrand.contextswitcher.car.business;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Represents a car as the business layer wants to see it.
 */
public class Car {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public Car(CarInternals carInternals, CarContextSwitcher carContextSwitcher) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
    }

    public CarInDb asCarInDb(DbPersister dbPersister) {
        return contextSwitcher.asCarInDb(dbPersister);
    }

    public CarAsRowInFile asRowInFile(FileWriter fileWriter) {
        return contextSwitcher.asRowInFile();
    }

    /**
     * Here is an example of business logic that operates on the internal representation.
     */
    public boolean isBig() {
        return internals.lengthInCentimeters >= 400;
    }

    public String toString() {
        return "Car{" + internals + "}";
    }
}
Here we have our pure business object.
Add the attributes internals and contextSwitcher and make sure they are set to private final.
Add the public constructor with the comment DO NOT USE with the signature shown in the example. Now add "as" methods of the other two context-switchable classes, in this case asCarInDb and asRowInFile. Then add your business methods such as isBig(). This is an ordinary class where the only thing you need to think of is to add this constructor!

Create the class CarFactory:
package nu.tengstrand.contextswitcher.car;

import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Responsible for creating our "context dependent" cars.
 */
public class CarFactory {

    public static Car createCar(int lengthInCentimeter, String name) {
        CarInternals carInternals = new CarInternals(lengthInCentimeter, name);
        return new CarContextSwitcher(carInternals).asCar();
    }

    public static CarInDb createCarInDb(int lengthInCentimeter, String name, DbPersister dbPersister) {
        CarInternals carInternals = new CarInternals(lengthInCentimeter, name);
        return new CarContextSwitcher(carInternals).asCarInDb(dbPersister);
    }

    public static CarAsRowInFile createCarAsRowInFile(String rowInFile) {
        return new CarAsRowInFile(rowInFile);
    }
}
This is the class responsible for creating instances of our context-switchable car classes. Make sure all the create methods are set as public static. The method signatures must include all parameters CarInternals needs plus any other parameters that your class needs. Car is an example that only wants a CarInternals while eg CarInDb also wants a CarDbPersister.

The classes Car and CarInDb is created by context-switch them via CarContextSwitcher. This works well since we can easily create a CarInternals. In the case CarAsRowInFile we don’t want to create a CarInternals due to this responsibility lies in the class itself, and we therefore use the constructor directly.

Now you can continue with adding more car classes specialized for a specific context, and if you follow the example, this means you also need to create CarInDb, CarAsRowInFile and Main!

To make the example complete, here are the other two representations of Car, we begin with CarInDb:
package nu.tengstrand.contextswitcher.car.persistence;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.banking.CarAsRowInFile;
import nu.tengstrand.contextswitcher.car.business.Car;

/**
 * Represents a car as a record in a table in the database.
 */
public class CarInDb {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;
    private final DbPersister dbPersister;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public CarInDb(CarInternals carInternals, CarContextSwitcher carContextSwitcher, DbPersister dbPersister) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
        this.dbPersister = dbPersister;
    }

    public Car asCar() {
        return contextSwitcher.asCar();
    }

    public CarAsRowInFile asRowInFile() {
        return contextSwitcher.asRowInFile();
    }

    public void save() {
        internals.primaryKey = dbPersister.save(internals.primaryKey, contextSwitcher);
        System.out.println("  '" + this + "' was saved to the database!");
    }

    public String toString() {
        return "CarInDb{" + internals + ", primaryKey=" + internals.primaryKey + "}";
    }
}

And here comes the last class CarAsRowInFile:
package nu.tengstrand.contextswitcher.car.banking;

import nu.tengstrand.contextswitcher.FileWriter;
import nu.tengstrand.contextswitcher.car.CarContextSwitcher;
import nu.tengstrand.contextswitcher.car.CarInternals;
import nu.tengstrand.contextswitcher.car.business.Car;
import nu.tengstrand.contextswitcher.car.persistence.DbPersister;
import nu.tengstrand.contextswitcher.car.persistence.CarInDb;

/**
 * Represents a car in the context of reading and writing it to a file.
 * The two constructors needs to keep the two representations,
 * internals and rowInFile, in sync.
 *
 * File format, e.g "0479,Volvo":
 *   0-3 = Length in centimeters
 *   5-  = Name
 */
public class CarAsRowInFile {
    private final CarInternals internals;
    private final CarContextSwitcher contextSwitcher;

    private String rowInFile;

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the factory!
     *
     * @param rowInFile row in a text file.
     */
    public CarAsRowInFile(String rowInFile) {
        this.rowInFile = rowInFile;

        internals = createCarInternals(); // We also need to set the internal representation.
        contextSwitcher = new CarContextSwitcher(internals);
    }

    private CarInternals createCarInternals() {
        int lengthInCentimeters = Integer.parseInt(rowInFile.substring(0,4));
        String name = rowInFile.substring(5);

        return new CarInternals(lengthInCentimeters, name);
    }

    /**
     * DO NOT USE THIS CONSTRUCTOR - use the CarFactory!
     */
    public CarAsRowInFile(CarInternals carInternals, CarContextSwitcher carContextSwitcher) {
        internals = carInternals;
        contextSwitcher = carContextSwitcher;
        setRowInFile(); // We also need to set the "row in file" representation.
    }

    private void setRowInFile() {
        String lengthCm = "000" + internals.lengthInCentimeters;
        rowInFile = lengthCm.substring(8-lengthCm.length()) + "," + internals.name;
    }

    public Car asCar() {
        return contextSwitcher.asCar();
    }

    public CarInDb asCarInDb(DbPersister dbPersister) {
        return contextSwitcher.asCarInDb(dbPersister);
    }

    public void appendToFile(FileWriter fileWriter) {
        fileWriter.appendToFile(rowInFile);
    }

    public String toString() {
        return "CarAsRowInFile{\"" + rowInFile + "\"}";
    }
}
It may be worth mentioning a detail of this class. It differs little from the other two as seen in CarFactory where the other types are sending in the entire internal representation in the parameter list, int lengthInCentimeter, String name, while CarAsRowInFile is created with the signature String rowInFile. This means that a CarAsRowInFile can be created in two ways, either by CarFactory where you send in a row from the file, which is our alternative representation of a car or via context-switching an instance of Car or CarInDb.

With these words I’m wishing you good luck with this pattern! A special thanks to James Trunk who helped me with the English text and provided constructive feedback. I would also like to thank Magnus Mickelsson, who took the time to look through the solution.