February 14, 2016

Java EE: Top 6 design patterns in practice (part 1 of 2)



In real world Java EE business software development projects, proper use of basic design pattern best practices will decide over software stability, development effort and long-term maintainability. Here I present 6 of my favorite design patterns as observed in real-world JEE software development, complete with anti-patterns and best practice solutions.

Note: This article does not cover higher-level architectural patterns such as MVC or DAO, many of which became de-facto deprecated with recent Java EE versions.

I will use a fictional example project as the context in which the following code samples are embedded in: It’s an online portal of a library where clients can make book reservations.

Template method

Motivation: You need to extend behavior from a parent class.

Let’s start with the most simple one which is also most often overlooked. Let’s say we have a base class, e.g. implementing a REST service endpoint which defines an add() method for REST PUT
public abstract class BaseResource<T extends Identifiable> {    
    @PUT
    public Response add(T entity) {
        entity = getService().save(entity);
        return Response.status(Response.Status.OK).entity(entity).build();
    }
    
    ...
}
which we happily use in sub-classes
@Path("customers")
@Stateless
public class CustomerResource extends BaseResource<Customer> {

}
until we discover that we need to do additional calculations in one of the sub-classes. For instance, as the REST endpoint implemented by the ReservationResource sub-class takes represents a nested resource (a reservation is part of a customer), we’d like to check whether the base resource (the customer) addressed in the path exists before invoking service#save().

Naïvely, we would implement this check by overriding the add() method, implementing additional logic before calling the super.add() method.
@Path("customers/{customerId}/reservations")
@Stateless
public class ReservationResource extends BaseResource<Reservation> {
    @Context UriInfo uri;

    @Override
    public Response add(Reservation entity) {
        Long customerId = getPathParam("customerId", Long.class);
        if (getService().findById(customerId) != null) {
            return super.add(entity);
        }
        else throw new IllegalArgumentException("Illegal customerId in resource path.");
    }
    
    private <T> T getPathParam(String key, Class <T> type) throws NumberFormatException {
        ...
    }
}
This, unfortunately, makes the design brittle. There are no (compile-time) guarantees that this class still implements the behavior as defined by the super-class. This really is a violation of Liskov’s substitution principle (LSP).

In the most simple case, you just forget to call the super method. In a more complex scenarios with many sub-classes or even a nested class hierarchy, with many classes adding and changing super class behavior at will, this will get out of hands quickly.

Here, the template method pattern comes to the rescue: Simply divide up functionality in the base class intro several smaller units (methods), and implement them as needed by the specific implementation.

In the example, the base class may provide a “hook” to later implement a check before the service#save() call:
public abstract class BaseResource<T extends Identifiable> {    
    @PUT
    public final Response add(T entity) {
        try {
            checkForeignKeyConstraints();
        } catch (ResourcePathForeignKeyNotFoundException ex) {
            throw ex.getCause();
        }
        entity = getService().save(entity);
        return Response.status(Response.Status.OK).entity(entity).build();
    }
    
    protected void checkForeignKeyConstraints() throws ResourcePathForeignKeyNotFoundException {
        // Hook. Do nothing by default
    }
    
    ...
}
which we then implement in the ReservationResource:
@Path("customers/{customerId}/reservations")
@Stateless
public class ReservationResource extends BaseResource<Reservation> {
    @Context UriInfo uri;

    @Override
    protected void checkForeignKeyConstraints() throws ResourcePathForeignKeyNotFoundException {
        Long customerId = getPathParam("customerId", Long.class);
        if (getService().findById(customerId) == null) {
            throw new ResourcePathForeignKeyNotFoundException("customerId");
        }
    }
    
    private <T> T getPathParam(String key, Class <T> type) throws NumberFormatException {
        ...
    }
}
(As a side note, we explicitly use a checked exception, not a boolean flag, for the check, to get even more compile-time safety.)

As a general rule of thumb, calling the super method is usually a sign of subpar design (a “code smell”) and a hint to apply the template method pattern.

State / Strategy

Motivation: You want to exchange behavior at runtime, possibly based on a model’s properties.

Allow me to treat the state and strategy pattern in this same section as in a Java EE environment, we’re typically referring to the same idea when talking about either of them: Decide at runtime which logic should be invoked. Here, this sounds like simply implementing an if / else-block, and this is typically also a perfectly valid solution.
public int calculateMaxNumberOfSimultaneousBorrows(Customer customer) {
    if (customer instanceof TrialCustomer) return 1;
    if (customer instanceof NormalCustomer) return 5;
    if (customer instanceof PlatinumCustomer) return 10;
    throw new IllegalArgumentException("Customer type not supported: " + customer.getClass());
}
But not if we can bind applicability of an algorithm to an object’s type, or its properties.

In above example, #calculateMaxNumberOfSimultaneousBorrows() depends on the Customer’s type, and defining its calculation outside of the Customer’s definition marks a violation of the open / closed principle (OCP): When adding a new Customer type, this central calculation need to be updated – a change in one place hence requires a change in another (I’ve actually used the same example in a previous article to illustrate the OCP).

Thus here, the strategy pattern should be applied. We need to define getMaxNumberOfSimultaneousBorrows() on the Customer base class:
public abstract class Customer {
    public abstract int getMaxNumberOfSimultaneousBorrows();
}
and override it in each subclass:
public class NormalCustomer extends Customer {
    @Override
    public int getMaxNumberOfSimultaneousBorrows() {
        return 5;
    }
}
Based on the sub-type of Customer a method holds, the correct algorithm is then invoked, and when defining a new sub-type, the compiler forces us to include its getMaxNumberOfSimultaneousBorrows() algorithm.

This is basic inheritance at work, and yet I see this pattern violated very often, which leads to code that is hard to maintain, but easy to break.

The main lesson learnt here is that except for a few cases of low-level reflection-based programming, usage of instanceof is always a code smell of bad inheritance / bad OOP application, and that you should use state / strategy in this situation.

Builder

Motivation: You want to build a structure dynamically, but compile-time safe.

The builder is probably one of my personal favorite design patterns because if properly applied, it can create highly readable, almost self-documenting fluent interfaces. This is a pattern which describes class relationships at compile time and thus helps us create compile-time-save structures.

It is most typically used when we have an object with a complex initialization routine, e.g. because it holds a lot of properties, the setting of individual properties must happen in a certain order, or setting one property may restrict the setting of other properties.

For instance, consider this Address definition:
public class Address {
    @NotNull private String street;
    @NotNull private String number;
    @NotNull private String zip;
    @NotNull private String city;
    private Country country;
    
    // Getters and setters...
}
Let’s assume that we want to make sure at compile time that we can only create Address objects with all the information set, except for country, which is optional. And we want to provide a self-documenting API to create an Address. We can do so by defining a series of builders:
public class Address {
    ...
    
    public static AddressBuilder street(String street, String number) {
        return new AddressBuilder(street, number);
    }
    
    public static class AddressBuilder {
        private final Address address = new Address();
        private String street;

        private AddressBuilder(String street, String number) {
            address.street = street;
            address.number = number;
        }
        
        public AddressBuilderLocation cityAndZip(String city, String zip) {
            address.city = city;
            address.zip = zip;
            return new AddressBuilderLocation(address);
        }
    }
    
    public static class AddressBuilderLocation {
        private final Address address;

        private AddressBuilderLocation(Address address) {
            this.address = address;
        }
        
        public AddressBuilderAll country(String countryCode) {
            address.country = Country.byCountryCode(countryCode);
            return new AddressBuilderAll(address);
        }
        
        public Address build() {
            return address;
        }
    }
    
    public static class AddressBuilderAll {
        private final Address address;
        
        private AddressBuilderAll(Address address) {
            this.address = address;
        }
        
        public Address build() {
            return address;
        }
    }
}
Which can then be invoked like so:
Address min = Address.street("First Street", "421").cityAndZip("Los Angeles", "90210").build();
Address all = Address.street("First Street", "421").cityAndZip("Los Angeles", "90210").country("US").build();
We just cannot go wrong here. Our builder’s API only allows us to build reasonable, easy-to-read object structures.

Note that we can make our life even easier by providing an even more simplified API through the builder (as with the country-by-String lookup example).

Of course, creating the builders itself may become complex, so we have to keep a balance between making the API as user-friendly as possible, and keeping the solution as lean as possible (keep it simple stupid (KISS)). (In fact, this address example would be quite over-engineered.)

Nevertheless, there are many excellent opportunities to use the builder pattern in a Java EE environment. In a more general sense, they can also be used to build rather abstract logic chains; they don’t even need to yield an instance at the end, but they can be used for their side-effects only (that would rather be a pure fluent interface then). This makes them especially useful to build any kind of “rule set” you want to fix at compile-time. I’ve created another example of such a builder-based rule set implementation in a previous blog post.

Pages: 1 2