Fluent interfaces while trying to make sense of Prototype Pattern
October 12th, 2011
Sometimes I don’t have great opportunities to apply some nice concepts, but I have some time to learn them.
While trying to make some sense of the Prototype Pattern, I was doing some experiments and I needed, of course, the tests to prove my experiments.
It was when coding the tests, trying to define the client interface (not user interface
), that to my mind came this article from Martin Fowler: Fluent Interface.
Well, what it matters are these two points:
- Making your tests first helps you, a lot, to define not only the behavior, but also importantly, the client API.
- Using Fluent Interface with a Builder, it’s a great way to send our message to the objects implementing the API.
Example of using a fluent interface:
package eu.jpereira.trainings.designpatterns.creational.prototype;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.CannotHaveZeroPartsException;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.CouldNotCloneLastObjectException;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.NeedToPackLastVehicleException;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.VehicleDoesNotHavePartsException;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.Shell;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.Tire;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.Vehicle;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.VehiclePartEnumeration;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.VehiclePartPropertiesEnumeration;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.Window;
/**
* @author jpereira
*
*/
public class ClientTest {
/**
* Integration Test
* @throws CouldNotCloneLastObjectException
* @throws CannotHaveZeroPartsException
* @throws NeedToPackLastVehicleException
* @throws VehicleDoesNotHavePartsException
*/
@Test
public void testCreateBUS() throws CouldNotCloneLastObjectException, CannotHaveZeroPartsException, NeedToPackLastVehicleException, VehicleDoesNotHavePartsException {
Client client = new Client();
//create a bus car
//Create props for tire
Properties tiresProps = new Properties();
tiresProps.put(VehiclePartPropertiesEnumeration.SIZE,10);
//Create props for shell
Properties shellProps = new Properties();
shellProps.put(VehiclePartPropertiesEnumeration.COLOR,"blue");
Properties windowProps = new Properties();
windowProps.put(VehiclePartPropertiesEnumeration.WIDTH,20);
windowProps.put(VehiclePartPropertiesEnumeration.WIDTH,20);
//client.createVehicle().with(new Tires()).times(4).
Vehicle vehicle = client.vehicleBuilder().createVehicle().with(new Tire(tiresProps)).times(3).with(new Window(windowProps)).times(8).with(new Shell(shellProps)).times(1).packIt();
//Get all windows
List<VehiclePart> parts = vehicle.getParts(VehiclePartEnumeration.WINDOW);
assertEquals(8, parts.size());
}
}
A hypothetical Builder implementation with a fluent interface:
package eu.jpereira.trainings.designpatterns.creational.prototype;
import java.util.ArrayList;
import java.util.List;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.CouldNotCloneLastObjectException;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.CannotHaveZeroPartsException;
import eu.jpereira.trainings.designpatterns.creational.prototype.exception.NeedToPackLastVehicleException;
import eu.jpereira.trainings.designpatterns.creational.prototype.model.Vehicle;
/**
* @author jpereira
*
*/
public class SimpleVehicleBuilder implements VehicleBuilder {
private List<VehiclePart> vehicleParts;
public SimpleVehicleBuilder() {
this.vehicleParts = createNewPartsBag();
}
@Override
public VehicleBuilder createVehicle() throws NeedToPackLastVehicleException{
//Just check this is allways the first call on the builder
if (vehicleParts.size()!= 0) {
throw new NeedToPackLastVehicleException();
}
return this;
}
@Override
public VehicleBuilder with(VehiclePart part) {
this.vehicleParts.add(part);
return this;
}
@Override
public VehicleBuilder times(int times) throws CouldNotCloneLastObjectException, CannotHaveZeroPartsException {
if (times==0) {
throw new CannotHaveZeroPartsException();
}
//get the last one and clone it xtimes
if ( this.vehicleParts.size()>0) {
VehiclePart lastObject = this.vehicleParts.get(this.vehicleParts.size()-1);
//add it xtimes
for (int i=0; i< times-1; i++) {
//new object
try {
this.vehicleParts.add((VehiclePart)lastObject.clone());
} catch (CloneNotSupportedException e) {
//Could not clone it. Wrap exception
throw new CouldNotCloneLastObjectException(e);
}
}
}
return this;
}
@Override
public Vehicle packIt() {
Vehicle vehicle = new Vehicle();
vehicle.setParts(this.vehicleParts);
//clear this reference for the chicle parts.
this.vehicleParts = createNewPartsBag();
return vehicle;
}
//Can be overriden by subclasses
protected List<VehiclePart> createNewPartsBag() {
return new ArrayList<VehiclePart>();
}
}
That’s it