-3

is this the right way to create builder pattern in java, if not what could be possible changes.

tried with static class


public class Multiverse {

    private UUID universeId;
    private String universeName;
    private String universeType;
    private Boolean humanExistence;

    public Boolean getHumanExistence() {
        return humanExistence;
    }

    private Multiverse() {
          throw new IllegalStateException("Can`t create object from constructor: try using builder");

    }


    private Multiverse(UUID universeId, String universeName, String universeType, Boolean humanExistence) {
        super();
        this.universeId = universeId;
        this.universeName = universeName;
        this.universeType = universeType;
        this.humanExistence = humanExistence;
    }


    public static class MultiverseBuilder{
            private UUID universeId;
            private String universeName;
            private String universeType;
            private Boolean humanExistence;

            public MultiverseBuilder makeUUId(UUID uuid) {
                this.universeId=uuid;
                return  this;
            }

            public MultiverseBuilder createUniverse(String univ) {
                this.universeName=univ;
                return this;

            }

            public MultiverseBuilder setUniverseType(String universeType ) {
                this.universeType=universeType;
                return this;
            }
            public MultiverseBuilder isHumanExists(Boolean humanExistence) {
                this.humanExistence=humanExistence;
                return this;
            }

        public Multiverse build() {
            return new Multiverse(universeId,universeName,universeType,humanExistence);
        }


    } 

    public UUID getUniverseId() {
        return universeId;
    }
    public String getUniverseName() {
        return universeName;
    }
    public String getUniverseType() {
        return universeType;
    }



}


Junit5 test

public class AssertionsTest6 {
    private static Logger logger=Logger.getLogger(AssertionsTest6.class.getName());

    Multiverse multiverse;

    @BeforeEach
    void init(){
        multiverse=new Multiverse.MultiverseBuilder()
                                 .makeUUId(UUID.randomUUID())
                                 .createUniverse("Earth")
                                 .setUniverseType("Big Bang")
                                 .isHumanExists(true)
                                 .build();  
        }

    @Test
    @DisplayName("Builder Testing")
    void TestBuilder() {
        assertEquals("Big Bang", multiverse.getUniverseType(), "test failed");
        logger.info("Builder testing");
    }



}

blocked reflection to make object directly from Multiverse class by doing this

private Multiverse() {
          throw new IllegalStateException("Can`t create object from constructor: try using builder");

    }

expected and actual are same. but not sure is this the best way to achieve objective. please correct or suggest me on this, [ expert advice required ]

3 Answers3

1

Design considerations:

  • force usage of builder (no direct instance creation allowed)?
  • immutability (what happens when invoking setters on builder after an instance has been created)?
  • reusability: allow builder to create multiple instances?

Example for a non-reusable builder which can be used to create exactly one instance, which is effectively immutable:

public class Multiverse {

    private UUID universeId;
    private String universeName;
    private String universeType;
    private Boolean humanExistence;

    private Multiverse() {
    }

    public UUID getUniverseId() {
        return universeId;
    }

    public String getUniverseName() {
        return universeName;
    }

    public String getUniverseType() {
        return universeType;
    }

    public Boolean getHumanExistence() {
        return humanExistence;
    }

    public static Builder aMultiverse() {
        return new Builder();
    }

    public static class Builder {

        private final Multiverse instance = new Multiverse();
        private boolean consumed;

        private Builder set(Consumer<Multiverse> access) {
            if (consumed) {
                throw new IllegalStateException("already consumed");
            }
            access.accept(instance);
            return this;
        }

        public Builder universeId(UUID universeId) {
            return set(x -> x.universeId = universeId);
        }

        public Builder universeName(String universeName) {
            return set(x -> x.universeName = universeName);
        }

        public Builder universeType(String universeType) {
            return set(x -> x.universeType = universeType);
        }

        public Builder humanExistence(Boolean humanExistence) {
            return set(x -> x.humanExistence = humanExistence);
        }

        public Multiverse build() {
            consumed = true;
            return instance;
        }
    }
}

The aMultiVerse() naming convention for accessing the builder allows static import of the builder factory method without clashing with other builder factory methods:

 Multiverse multiverse = aMultiverse()
     .universeId(UUID.randomUUID())
     .universeName("Earth")
     .universeType("Big Bang")
     .humanExistence(true)
     .build();
Peter Walser
  • 15,208
  • 4
  • 51
  • 78
  • Prefixes like the 'a' are not good practice. What does 'a' mean? Or should I choose to call the `bMultiverse()` method? I don't know since I don't know what 'b' means. Why don't call it `immutableMultiverseBuilder()`? Now I would know what makes this method so special in contrast to `mutableMultiverseBuilder()`. A naming convention is a good thing and valuable recommendation, but this C-style Hungarian like prefix notation should be avoided. It's a relic of times where compilers didn't support type checking. Most humans like to read words. – BionicCode Jun 20 '19 at 21:56
0

Some notes about your approach:

  • I don't think that it makes any sense to 'block' users from creating an instance via reflection. Since you defined a constructor there is no no-arg constructor anyways. Thus instances can only be created by passing a builder.
  • I like to pass the builder instance to the constructor. This way you will have readable code even if the class has a lot of fields.
  • I like to call my builder just Builder. It's a nested class und you will probably always write Multiverse.Builder.
  • I like the actual class to have a factory method for the builder so I can just write Multiverse.builder() and start populating the fields.
  • The methods of the builder class should have a consistent naming scheme.

This is how my builders usually look like:

public class Multiverse {

    private final UUID universeId;

    private final String universeName;

    private final String universeType;

    private final Boolean humanExistence;

    private Multiverse(Builder builder) {
        this.universeId = builder.universeId;
        this.universeName = builder.universeName;
        this.universeType = builder.universeType;
        this.humanExistence = builder.humanExistence;
    }

    public static Builder builder() {
        return new Builder();
    }

    public UUID getUniverseId() {
        return universeId;
    }

    public String getUniverseName() {
        return universeName;
    }

    public String getUniverseType() {
        return universeType;
    }

    public Boolean getHumanExistence() {
        return humanExistence;
    }

    public Builder toBuilder() {
        return new Builder(this);
    }

    public static class Builder {

        private UUID universeId;

        private String universeName;

        private String universeType;

        private Boolean humanExistence;

        private Builder() {}

        private Builder(Multiverse multiverse) {
            this.universeId = multiverse.universeId;
            this.universeName = multiverse.universeName;
            this.universeType = multiverse.universeType;
            this.humanExistence = multiverse.humanExistence;
        }

        public Builder withUniverseId(UUID universeId) {
            this.universeId = universeId;
            return this;
        }

        public Builder withUniverseName(String universeName) {
            this.universeName = universeName;
            return this;
        }

        public Builder withUniverseType(String universeType) {
            this.universeType = universeType;
            return this;
        }

        public Builder withHumanExistence(Boolean humanExistence) {
            this.humanExistence = humanExistence;
            return this;
        }

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

    }

}

Creating multiverses works like this:

Multiverse multiverse1 = Multiverse.builder()
    .withUniverseId(UUID.fromString("550e8400-e29b-11d4-a716-446655440000"))
    .withUniverseName("my first multiverse")
    .withUniverseType("type a")
    .withHumanExistence(true)
    .build();

If you decide to edit this multiverse later, you can do it like this:

Multiverse multiverse2 = multiverse1.toBuilder()
    .withUniverseId(UUID.fromString("759e947a-7492-af67-87bd-87de9e7f5e95"))
    .withUniverseName("my second multiverse")
    .build();

This would fulfill the following assertions:

assert multiverse1.getUniverseId().equals("550e8400-e29b-11d4-a716-446655440000");
assert multiverse1.getUniverseName().equals("my first multiverse");
assert multiverse1.getUniverseType.equals("type a");
assert multiverse1.getHumanExistence == true;
assert multiverse2.getUniverseId().equals("759e947a-7492-af67-87bd-87de9e7f5e95");
assert multiverse2.getUniverseName().equals("my second multiverse");
assert multiverse2.getUniverseType.equals("type a");
assert multiverse2.getHumanExistence == true;
stevecross
  • 5,588
  • 7
  • 47
  • 85
  • This is not how builders usually look like. Why do you add a (circular) dependency to the product class (`Multiverse` in your example)? Usually the builder collects data via the setters and stores those values. It then constructs an instance of the product when `Build()` is called. In your case the builder is constructed with the product he should create(?) and the product is constructed from the builder that should create it. Normally you call `nwe Builder()`. Then `builder.setUniverseName(string)` and finally get the product by calling `builder.Build()`. That's how it is done usually. – BionicCode Jun 20 '19 at 21:28
  • Sorry, I can't follow. An empty builder can be created with the static `builder()` method. If you want to create your final class you call `build()` on it. That gives you an immutable instance of `Multiverse`. If you want to mutate it afterwards, you can create a new builder from that instance by calling `toBuilder()`. There is no circular dependency. – stevecross Jun 20 '19 at 21:42
  • I never read _immutable_ in the question. Maybe I missed it or you made it up. It is not a requirement for a builder to return immutable products. It's a special case. He only asked about the "right way" of following the builder pattern coming up with some fancy version with nested classes. I understood it like he wanted the standard Builder Pattern and not a special case. Your version is not the standard version. That was my point. But you are right. This your builder is about an immutable product and it will work. – BionicCode Jun 20 '19 at 22:24
  • I have to apologize. But nevertheless, the dependency is circular since both types depend on each other. You can proof it by moving the builder in to a different package (e.g. helpers) or (in your case) just by looking at the constructors. – BionicCode Jun 20 '19 at 22:24
  • Sure, but it's kind of an atomic unit. The builder can not exist without the product it should build. And in this case the only way to create a `Multiverse` is via the builder. Thus removing the builder would make no sense. However, the question is way to broad and I think there is no right answer for it. I just gave an opinionated example for a builder that produces immutable instances. – stevecross Jun 21 '19 at 10:10
  • Yes, it's all opinion based. You are right. That's why we have best practices and no laws. But to make the use of `Multiverse`-like classes more convenient I would remove the class nesting and remove the direct coupling of the helper and the original object of interest. When both can exist in parallel and independent, this would be the best. – BionicCode Jun 21 '19 at 12:56
  • This way you are not forced to use the builder to get instances of the product type. I never came across this restriction in frameworks or libraries. You would only be forced to initialize the immutable object instance via the constructor. If this requires to much parameters, then you would introduce an args object (e.g. `MultiverseArgs`). This way everything has become more flexible. You can even move the builder into a helpers package. A type that can only be instantiated via a builder (due to the private constructor) is difficult to use in certain dependency injection scenarios too. – BionicCode Jun 21 '19 at 12:56
0

this is the final solution i think , Thank you #stevecross

public final class BootServer { // step #1 make class and fields final

private final Integer port;
private final String serverName;
private final String serverType;
private final String listenerType;



private BootServer(Builder builder) {// step #2 create private constructor
    this.port = builder.port;
    this.serverName = builder.serverName;
    this.serverType = builder.serverType;
    this.listenerType=builder.listenerType;

}

 public static Builder builder() {//Step#3 create static builder method to return Builder
        return new Builder();
    }

public static final class Builder {//Step#4 create public static builder class

    private Integer port;
    private String serverName;
    private String serverType;
    private String listenerType;

    private Builder(){

    }
    public BootServer build() {//Step#5 create build method to return BootServer Object with this object
        return new BootServer(this);
    }


    public Builder addServerPort(Integer port) {//Step#6 finally create all build method to set values to main class 
        this.port=port;
        return this;
    }


    public Builder addServerName(String name) {
        this.serverName=name;
        return this;
    }

    public Builder setServerType(String serverType) {
        this.serverType=serverType;
        return this;
    }

    public Builder setListenerType(String listenerType) {
        this.listenerType=listenerType;
        return this;
    }

}

@Override
public String toString() {
    return "BootServer [port=" + port + ", serverName=" + serverName + ", serverType=" + serverType
            + ", listenerType=" + listenerType + "]";
}

}