Set a value at most once with the builder pattern
Asked Answered
L

4

10

Is there a standard practice in Java, while using the builder pattern, to ensure that a member variable is set at most once. I need to make sure that the setter is called 0 or 1 times but never more. I would like to throw a RuntimeException of some type but am worried about synchronization issues as well as best-practices in this area.

Logarithmic answered 26/8, 2014 at 3:48 Comment(0)
C
8

There's nothing wrong with raising an exception if a user calls a method in an illegal way like you describe, but it's not terribly elegant. The idea behind the builder pattern is to let users write fluent, readable object definitions, and compile-time safety is a big part of that. If users can't be confident the builder will succeed even if it compiles, you're introducing additional complexity users now need to understand and account for.

There are a couple of ways to accomplish what you're describing, lets explore them:

  1. Just let users do what they want

    One nice thing about builders is they can let you construct multiple different objects from the same builder:

    List<Person> jonesFamily = new ArrayList<>();
    Person.Builder builder = new Person.Builder().setLastName("Jones");
    
    for(String firstName : jonesFamilyFirstNames) {
      family.add(builder.setFirstName(firstName).build());
    }
    

    I assume you have a good reason for forbidding this sort of behavior, but I'd be remiss if I didn't call out this useful trick. Maybe you don't need to restrict this in the first place.

  2. Raise an Exception

    You suggest raising an exception, and that will certainly work. Like I said, I don't think it's the most elegant solution, but here's one implementation (using Guava's Preconditions, for extra readability):

    public class Builder {
      private Object optionalObj = null;
      // ...
    
      public Builder setObject(Object setOnce) {
        checkState(optionalObj == null, "Don't call setObject() more than once");
        optionalObj = setOnce;
      }
      // ...
    }
    

    This raises an IllegalStateException, so you can just call throw new IllegalStateException() if you aren't using Guava (you should be... :) ). Assuming you're not passing builder objects around between threads, you should have no synchronization issues. If you are, you should put some further thought into why you need the same builder in different threads - that's almost surely an anti-pattern.

  3. Don't provide the method at all

    This is the cleanest, clearest way to prevent the user from calling a method you don't want them to - don't provide it in the first place. Instead, override either the builder's constructor or build() method so they can optionally pass the value at that time, but at no other time. This way you clearly guarantee the value can be set at most once per object constructed.

    public class Builder {
      // ...
    
      public Obj build() { ... }
      public Obj build(Object onceOnly) { ... }
    }
    
  4. Use different types to expose certain methods

    I haven't actually done this, and it might be more confusing than it's worth (in particular, you'll likely need to use a self-bounding generic for the methods in Builder), but it came to mind while I was writing and could be very explicit for certain use cases. Have your restricted method in a subclass of the builder, and that method returns the parent type, therefore preventing the caller from re-calling the method. An example might help, if that didn't make sense:

    public class Builder {
      // contains regular builder methods
    }
    
    public class UnsetBuilder extends Builder {
      public Builder setValue(Object obj) { ... }
    }
    
    // the builder constructor actually returns an UnsetBuilder
    public static UnsetBuilder builder() { ... }
    

    Then we can call something like:

    builder().setValue("A").build();
    

    But we'd get a compile-time error if we tried to call:

    builder().setValue("A").setValue("B").build();
    

    because setValue() returns Builder, which lacks a setValue() method, therefore preventing the second case. This would be tricky to get exactly right (what if the user casts the Builder back to a UnsetBuilder?) but with some effort would do what you're looking for.

Chimborazo answered 26/8, 2014 at 5:19 Comment(3)
The second one is probably the best solution to my problem. Synchronization is not a strict requirement, it was just a nice to have. Never know how the code will be used when it leaves my desk. Probably can just add a "not thread safe" comment in the javadocs somewhere. The original problem I was trying to solve was with parsing xml into a specific class structure; the xml structure itself can take on many forms. I was looping and populating the class using the builder. The one requirement that exists is that if a certain exists than it can only be set once.Logarithmic
Ensuring builders are thread-safe is a high requirement, and one I don't think you should generally provide. Guava's Immutable* builders look to not be thread-safe, for instance (e.g. ImmutableList.Builder is backed by an array that can be resized). Any user that attempts to use an arbitrary builder object across threads is making a mistake, in my opinion.Chimborazo
Another (personal favourite) variation of #4 would be Marco Castigliego's Step Builder pattern, where essentially the Builder's methods are defined in interfaces, each interface represents a "step" in the build process, and each method returns a subsequent "step's" interface. The Builder itself implements those interfaces, thus exposing a given subset of its overall functionality at each invocation. Common methods (e.g. reset()) can still be made available, by being placed in a base interface extended by others.Geostrophic
L
3
Object objectToSet;

boolean isObjectSet = false;

void setObject(Object object) throws RuntimeException {
    if(!isObjectSet) {
        objectToSet=object;
        isObjectSet=true;
    } else {
       throw new RuntimeException();
    }

}

This is the standard practice.

Lite answered 26/8, 2014 at 3:56 Comment(2)
This does not satisfy the OPs synchronization worries, though.Anchorage
@Anchorage I think he means thread safety when throwing the exception. This would be just fine, since he can catch the exception. Otherwise, just add synchronized before void...Lite
O
0

You can define your variable as final.

final MyClass object;

EDIT: If it sets more than once you get a compile-time error.


Or as mentioned by other, you can check your variable state in the setter and for thread-safety, declare the setter method as synchronized.

public synchronized void setMyObject(Object object)
{
    if (this.myObject == null)
    {
        this.myObject = object;
    } else {
        throw new RuntimeException();
    }
}

Use the setter method even in constructor or anywhere else.

Note: Using synchronized methods may lead to performance instability in huge processing.

Orthopsychiatry answered 26/8, 2014 at 4:3 Comment(3)
If he is using a setter outside the constructor this will not work.Lite
Additionally, using final incorrectly will result in compile-time errors, not runtime exceptions (ignoring reflection of course).Chimborazo
won't work as the OP wanted to make sure that the setter is called 0 or 1 times ... marking a field as final forces you to set a value at least once!Endogen
M
0

You can use the Builder pattern outlined in Effective Java. This doesn't quite meets your specifications, but I think it should meet your needs. Let me know.

class Foo {
    private final Object objectToSet;

    private Foo(Foo.Builder builder) { 
        objectToSet = builder.getObjectToSet();
    }

    public static class Builder {
        private Object objectToSet; 

        public Builder() { } 

        private Object getObjectToSet() {
            return objectToSet;
        }

        public Builder objectToSet(Object objectToSet) { 
            this.objectToSet = objectToSet; 
            return this; 
        }

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

Then later:

Object someObject = 10;
Foo foo = new Foo.Builder()
    .objectToSet(someObject)
    .build(); 
// A Foo's `objectToSet` can only be set once

This allows you to define a class (this case Foo) that has properties that can only be set once. As a bonus, you can make the the Foo truly immutable.

Monika answered 26/8, 2014 at 4:33 Comment(4)
OP is already using the builder pattern. He wants to prevent the case where someone calls, to use your example, new Foo.Builder().objectToSet(someObject).objectToSet(someOtherObject).build();.Chimborazo
What is stopping us from doing builder.objectToSet(foo).objectToSet(bar) ?Anchorage
@Chimborazo It's open for interpretation what exactly is meant by "builder pattern" and "make sure the setter is called 0 or 1 times" - the resulting final object out of Builder.build() infact can't have a setter called, because it doesn't have one defined. Ultimately up to the OP for interpretation.Monika
@Monika "builder pattern" is admittedly a broad phrase, but ultimately refers to a technique of constructing objects without needing to explicitly define every possible permutation of method signature. That isn't really open for interpretation. Similarly, since the OP is talking about a builder, and as you note the object being built (hopefully) doesn't have a setter, it's fairly clear we're discussing preventing a setter of the builder from being called more than once. You are effectively saying "Use the builder pattern" in response to a "How do I do X with the builder pattern" question.Chimborazo

© 2022 - 2024 — McMap. All rights reserved.