young-ceo
young-ceo

Reputation: 5374

Abstract class for builder pattern

I have existing model classes that always use builder pattern like this:

public class Model {
    public static class Builder {
        private boolean isValid;
        private List<String> errorMessagesOrNull;

        public Builder setIsValid(final boolean isValid) {
            this.isValid = isValid;
            return this;
        }

        public Builder setErrorMessages(final List<String> errorMessages) {
            this.errorMessagesOrNull = errorMessages;
            return this;
        }

        public List<String> getErrorMessages() {
            return this.errorMessagesOrNull == null ? new ArrayList<>() : this.errorMessagesOrNull;
        }

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

    private boolean isValid;
    private List<String> errorMessages;

    private Model(final Builder builder) {
        this.isValid = builder.isValid;
        this.errorMessages = builder.getErrorMessages();
    }

    public boolean getIsValid() {
        return isValid;
    }

    public List<String> getErrorMessages() {
        return errorMessages;
    }
}

As you see, the model classes always have isValid and errorMessages. I want to write an abstract class to minimize the repeated logic for those model classes.

So I came up like this abstract class:

public abstract class AbstractModel<T extends AbstractModel<T>> {

    public static abstract class Builder<T> {
        private boolean isValid;
        private List<String> errorMessagesOrNull;

        public Builder<T> setIsValid(final boolean isValid) {
            this.isValid = isValid;
            return this;
        }

        public Builder<T> setErrorMessages(final List<String> errorMessages) {
            this.errorMessagesOrNull = errorMessages;
            return this;
        }

        public List<String> getErrorMessages() {
            return this.errorMessagesOrNull == null ? new ArrayList<>() : this.errorMessagesOrNull;
        }

        public abstract T Build();
    }

    private boolean isValid;
    private List<String> errorMessages;

    private AbstractModel(final Builder<T> builder) {
        this.isValid = builder.isValid;
        this.errorMessages = builder.getErrorMessages();
    }

    public boolean getIsValid() {
        return isValid;
    }

    public List<String> getErrorMessages() {
        return errorMessages;
    }
}

But it's not really working as I intended. When I extends the abstract class:

public class Model extends AbstractModel<Model> {
    // Empty here since all fields are extended
}

I cannot do something like:

Model model = new Model.Builder.setIsValid(true).Build();

I want the abstract class has Builder static class, so that I don't need to write the static class Builder every time.

Please advise.

Upvotes: 6

Views: 15598

Answers (2)

user7338524
user7338524

Reputation:

I think that there is a huge flaw in your logic. The program itself doesn't really make any sense at all. Why do you construct a Model with the Builder class in the first place? I think it is better to show you how you should have written your program, instead of just "bodging" it together. Alright, let us start with the Model class.

Let's say the Model class cannot be constructed without a Builder. Would it then make sense to add the Builder class into the Model class? Short answer: no, it wouldn't. Instead, the Builder class should contain the Model class as a non-static internal class.

/**
 * The {@code Builder} can construct new instances of the {@code Model} class.
 *
 * @see Model
 */
public class Builder
{
    private final String[] log;

    /**
     * The {@code Model} class can do something. You can only construct it through a {@code Builder}.
     *
     * @see Builder
     */
    public class Model
    {
        private final Builder builder;

        /**
         * Constructs a new {@code Model} with the specified argument.
         *
         * @param builder the {@code Builder} that constructed the model.
         */
        public Model(final Builder builder)
        {
            this.builder = builder;
        }

        /**
         * Returns the associated {@code Builder}.
         *
         * @return the builder that constructed the model.
         */
        public Builder getBuilder()
        {
            return this.builder;
        }
    }

    /**
     * Constructs a new instance of the {@code Builder} class with the specified argument.
     *
     * @param log the log of the {@code Builder}.
     */
    public Builder(final String... log)
    {
        this.log = log;
    }

    /**
     * Tries to {@code build} a new instance of the {@code Model} class.
     *
     * @return the constructed {@code Model}.
     */
    public Model build()
    {
        return new Model(this);
    }

    /**
     * Returns the log of the {@code Builder}.
     *
     * @return an log.
     */
    public String[] getLog()
    {
        return this.log;
    }

    /**
     * Determines whether or not the {@code Builder} is valid.
     *
     * @return {@code true} when the specified {@code log} is not {@code null}; {@code false} otherwise.
     */
    public boolean isValid()
    {
        return this.log != null;
    }
}

No class other than the Builder can construct a Model. However, if you construct a new instance of the Builder class and get the result of invoking the build method, you'll have access to all public variables and methods.

If you know want to construct a Model, you can do that just like that:

Builder.Model model = new Builder().build();

If you don't want the Builder. prefix, just add an import statement that imports the Model class.

import organisation.projectname.pathToBuilder.Builder.Model;

Upvotes: 4

dehasi
dehasi

Reputation: 2773

You also need to implement the Builder.

public class Model extends AbstractModel<Model>{


    private Model(final Builder builder) {
        super(builder);
    }

    public static class Builder2 extends AbstractModel.Builder<Model> {

        @Override
        public Model Build() {
            return new Model(this);
        }
    }
}

then it possible to call

Model model = new Model.Builder2().Build();

EDIT

Also, the constructor of AbstractBuilder also must be protected.

  protected AbstractModel(final Builder<? extends Builder<T>> builder) {
        this.isValid = builder.isValid;
        this.errorMessages = builder.getErrorMessages();
    }

Upvotes: 5

Related Questions