Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make all Builder methods required?

Tags:

java

I am constructing a car class that has an engine, gearbox, clutch etc. I don't want a bloated constructor that takes 7 parameters, so I decided to use the builder pattern. All the parts are required. However, how do I make the user of the Car class use all the parts' setters, as they are all mandatory? Throw exceptions?

public class Car {

    private Engine engine;
    private Chassis chassis;
    private GearBox gearBox;
    private Coupe coupe;
    private Exterior exterior;
    private Interior interior;
    private Clutch clutch;

    public Car(Builder builder) {
        engine = builder.engine;
        chassis = builder.chassis;
        gearBox = builder.gearBox;
        coupe = builder.coupe;
        exterior = builder.exterior;
        interior = builder.interior;
        clutch = builder.clutch;
    }

    public static class Builder {

        private Engine engine;
        private Chassis chassis;
        private GearBox gearBox;
        private Coupe coupe;
        private Exterior exterior;
        private Interior interior;
        private Clutch clutch;


        private Car build() {
            return new Car(this);
        }

        public Builder setEngine(@NonNull Engine engine) {
            this.engine = engine;
            return this;
        }

        public Builder setChassis(@NonNull Chassis chassis) {
            this.chassis = chassis;
            return this;
        }

        public Builder setGearBox(@NonNull GearBox gearBox) {
            this.gearBox = gearBox;
            return this;
        }

        public Builder setCoupe(@NonNull Coupe coupe) {
            this.coupe = coupe;
            return this;
        }

        public Builder setExterior(@NonNull Exterior exterior) {
            this.exterior = exterior;
            return this;
        }

        public Builder setInterior(@NonNull Interior interior) {
            this.interior = interior;
            return this;
        }

        public Builder setClutch(@NonNull Clutch clutchs) {
            this.clutch = clutchs;
            return this;
        }

    }


}

I want the user so call ALL of the builder setters, not an optional subset of them. How do I do that?

Is there another way to construct a car without having a huge constructor that takes so many parameters?

EDIT: I looked at The builder pattern and a large number of mandatory parameters but there is no solution there that prevents huge constructors.

like image 314
Kaloyan Roussev Avatar asked Nov 12 '15 14:11

Kaloyan Roussev


People also ask

What does builder () build () do?

Builder is a creational design pattern, which allows constructing complex objects step by step.

How do you build builder classes?

First of all you need to create a static nested class and then copy all the arguments from the outer class to the Builder class. We should follow the naming convention and if the class name is Computer then builder class should be named as ComputerBuilder .

What are builder methods?

The builder pattern is a design pattern designed to provide a flexible solution to various object creation problems in object-oriented programming. The intent of the Builder design pattern is to separate the construction of a complex object from its representation. It is one of the Gang of Four design patterns.


2 Answers

Builders are for things where lots of parts are optional or you have many different configurations. The build() method then makes sure that the specific configuration works. A HTML builder makes sense, a string builder not so much.

If there are no optional parts, you have these options:

  • Get rid of the builder and use a constructor that requires all parts. Classic solution, least readable and hard to extend.

  • You can throw an exception in the build() method for each missing part. That's a lot of code to write and kind of goes against the pattern.

  • You can add methods with more than a single argument to the builder. That's a mix of the two above. You're still using the builder pattern (so you can easily add more configurations later) but your API also communicates more clearly which parts are mandatory.

    As an example, you might get an engine in the future which already contains the gearbox or which requires a specific gearbox (i.e. when you use this engine, you also select the gearbox). To implement this, you'd create a second builder method which just asks for the engine and determines the gearbox automatically.

  • Use a factory with protected methods which build the parts. The factory will make sure all parts are supplied to the car. If you want to replace a part, you can override the protected method. This works well if you have lot of defaults and only 2-3 useful configurations.

  • Use several builders. Instead of build everything from individual screws, create your car from larger building blocks: propulsion, interior, body. Create your car from those three with a constructor (three parameters is good). Now you can use three builders to create those three. Try to find a balance between required and optional elements.

  • Chain builders as explained by mlk below. This forces the user to fill in all parts (since you can only call the build() at the end of the chain). Main drawbacks here are: A lot of code to write and hard to follow from the user perspective since the code is spread over many classes. jOOQ is an example here; the project implements the SQL syntax as a chain of builders.

Remember what the builder pattern tries to solve: They make it easy to add more parts since all existing code doesn't need to change if the new part is optional. If the new part is mandatory, the builder pattern becomes a liability: With a huge (unreadable) constructor, you get compile errors in all places that needs fixing. The builder will fail at runtime in this case; you need an IDE to find all the places which need fixing.

like image 169
Aaron Digulla Avatar answered Sep 27 '22 19:09

Aaron Digulla


If the primary reason for this is to have a fluent API rather than removing a bloated constructor then you could chain builders together:

class Engine {}
class Door {}
class Car {
    Car(Engine engine, Door door) {

    }
}

class CarBuilder {
    private Engine engine;

    public CarWithEngineBuilder withEngine(Engine engine) {
        this.engine = engine;
        return new CarWithEngineBuilder();
    }

    class CarWithEngineBuilder {
        private Door door;


        public CarWithEngineAndDoor withDoor(Door door) {
            this.door = door;
            return new CarWithEngineAndDoor();
        }

        class CarWithEngineAndDoor {
            public Car build() {
                return new Car(engine, door);
            }
        }
    }
}

class TestStuff {
    {
        Car c = new CarBuilder().withEngine(new Engine()).withDoor(new Door()).build();
    }
}

Or if your main concern is the size of the constructor, maybe the constructor is telling you something, and you could look at the class and see is some parts are logically "together". I.e. are Engine, Gears & Brake are one part of larger component? Should the car be DriveSystem and Chassis (which includes an Exteriorand Interior). Then constructor for Car has a manageable number of parameters, as does DriveSystem and Chassis?

like image 33
Michael Lloyd Lee mlk Avatar answered Sep 27 '22 19:09

Michael Lloyd Lee mlk