Skip to content

Latest commit

 

History

History
143 lines (117 loc) · 8.31 KB

README.md

File metadata and controls

143 lines (117 loc) · 8.31 KB

Car dealer [Builder | Factory method]

Description

Module Vehicles is a sample implementation of two creational design patterns: Builder and Factory method. This category of patterns helps us simplify the initialization task when an Object has too many attributes to configure –Builder– or when creating a new instance usually requieres the same certain group of params –Factory method–.

We have a vehicle selling business. As you probably already know, there are a lot of different vehicles out there; even multiple models from the same branches. This is because every vehicle has different features. Those characteristics go from the style (type of vehicle) to the extras or the colour. The branches design their models, usually leaving a few open options for clients to choose among some of these features, like the fuel used, or even sometimes the number of doors.

Builder

If you think about it, designing –or configuring– a model is a tedious task. There's a lot of things to decide; so it would be useful to have some help instantiating them. And here is where the Builder class appears. As a subclass of Model, we have the ModelBuilderclass, with the same attributes, but just one constructor with the minimum required params for a Model to be operative, and setter methods for every other configuration we want to add, always returning their own instance. This is the key for the Builder pattern, because we can make a chain with the different set invocations, as they all return the object we are configuring; and then, when we finish, we call the build() method, that returns the new instance of Model.

public static class ModelBuilder {

    // Other attributes (same as Model class), a Constructor with Model's required attributes and setter methods.

    public Model build() {
        return new Model(this);
    }
}

The last piece of these pattern is to add that Constructor with the ModelBuilder arg to the Model class, and also a public static builder method to make it accesible.

public class Model {

    // Other attributes and methods.

    private Model(final @NotNull ModelBuilder modelBuilder) {
        this.name = modelBuilder.getName();
        this.style = modelBuilder.getStyle();
        this.basePrice = modelBuilder.getBasePrice();
        this.availableColours = modelBuilder.getAvailableColours();
        this.availableDoorsCount = modelBuilder.getAvailableDoorsCount();
        this.availableExtras = modelBuilder.getAvailableExtras();
        this.availableFuels = modelBuilder.getAvailableFuels();
        this.availableHorsePowers = modelBuilder.getAvailableHorsePowers();
    }

    public static ModelBuilder builder(final String name, final Style style, final float basePrice) {
        return new ModelBuilder(name, style, basePrice);
    }
}

You can see it working in Store class, but it would be as easy as following the body of this improvised createModel() method:

public class ModelCreator {

    public static Model createModel() {
        return model = Model.builder("Model name", Style.CARGO, 5850f)
                .setAvailableColours(Colour.BLACK, Colour.BLUE, Colour.BROWN)
                .setAvailableFuels(Fuel.GASOLINE, Fuel.DIESEL)
                // Other additional features desired.
                .build();
    }
}

So readable, so easy to handle, and if you check the setter methods declared on ModelBuilder class you'll see there's a bunch of useful tools to create or modify the specifications of the object. The characteristics of this Model class are mostly collections of enum types in order to keep the example bounded and simple, but realize these could be even much more profitable when we require many String, boolean or numeric params, whose order could be hard to read and distinguish in a common Constructor method.

It's important to remember that ModelBuilder.build() method invokes Model Constructor method. So, you can use the same ModelBuilder instance to create multiple models with the same attributes' values, but notice that you won't get a reference to the same object, but a new instance each time.

Factory method

Now that we have some defined models, it's time to share them and let people choose the ideal configuration for their lifestyle. It's at this moment when we realize that's a lot for them. People are not always comfortable making decisions to buy something, because it raises our stress levels. So it would be nice to have some «pre-configured packages» with the most popular features, just like the travel agencies do for saving us the work of planing every step of our holidays. Imagine that you could instantiate a complete new Vehicle by calling a method giving just one param with the «package name». Well, that's a Factory method pattern, implemented in VehicleFactory class in this example. We ask for the pre-configured package and the colour. The package is an enum subclass containing the reference to the inheritor class ofVehicle corresponding to the package, whose implementation has a complete set of features already defined in the Constructor method (no need to call setters nor specify params) and it returns an instance. As simple as it sounds. We can create as much "packages" as inheritors of Vehicle we want to have pre-configured. In this example, we let the colour election out of the pre-configuration for clients to be able to still pick that feature. As you see, you can select what you want to configure automatically and what you want to ask for.

Regarding the VehicleFactory class, you have to know that this is not the traditional implementation of the static method for getting the desired instance. This is a totally parametrized version which would never be modified with upcoming new inheritors of Vehicle –new «packages»–, because in that case we would just add new values to the VehiclePackage subclass. It uses reflection to access the required class names and constructor methods, and you should know that implies a significant cost in processing resources during runtime. It's also more «insecure» exceptions we have to prevent while calling a method whose reference is obtained from a variable. The most popular way to implement this pattern is creating a switch loop, having one case clause for each type of instance/inheritor. For our particular example, this would have been the code:

public class VehicleFactory {

    public static Vehicle getVehicle(@NotBlank String packageName, @NotBlank String colour) {
        switch (packageName) {
            case "common familiar":
                return new CommonFamiliarVehicle(Store.getModelByName("Urban Family"), colour);
            case "practical workers":
                return new PracticalWorkersVehicle(Store.getModelByName("Hard Labourer"), colour);
            case "cool sports":
                return new CoolSportsVehicle(Store.getModelByName("Hot Player"), colour);
            default:
                String errorMessage = String.format("Unrecognized value for pack ame [%s] or colour [%s].", packageName, colour);
                log.error(errorMessage);
                throw new UnsupportedOperationException(errorMessage);
        }
    }
}

Of course, you can still end up with a third version somewhere in the middle of these two, combining the use of the switch loop and the enum class.

Subjects, technologies and contents

  • Single Responsibility Principle (S from SOLID).
  • Use of Assert class's methods for Vehicle features validation throughout instantiation process.