5

I have written a class using Joshua Bloch's Builder pattern, which is similar to this Pizza example:

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

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

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

but PMD reported 2 warnings:

  1. (Pointing to method Builder.build()) Avoid instantiation through private constructors from outside of the constructor's class. Instantiation by way of private constructors from outside of the constructor's class often causes the generation of an accessor. A factory method, or non-privitization of the constructor can eliminate this situation. The generated class file is actually an interface. It gives the accessing class the ability to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. This turns a private constructor effectively into one with package scope, and is challenging to discern.
  2. Class cannot be instantiated and does not provide any static methods or fields. A class that has private constructors and does not have any static methods or fields cannot be used.

Should I just ignore these warnings?

Another question: the private fields in class Pizza and Builder are duplicate. This will be annoying when the number of private fields getting bigger. Is there any way to avoid it?

skaffman
  • 398,947
  • 96
  • 818
  • 769
chance
  • 6,307
  • 13
  • 46
  • 70
  • for 1. you could maybe define your Pizza constructor as package-protected. This should also solve 2. – Rom1 May 30 '11 at 09:33
  • 9
    The pattern is a good one; PMD is just being dumb. I'd ignore it, personally. – skaffman May 30 '11 at 09:43
  • just a side note: I've been using a tiny framework called [Make-It-Easy](http://code.google.com/p/make-it-easy/) for a while, which helps to build this *builders* and the code is quite readable. I use it mostly for my unit tests, but it could be definitely used for production code. – Augusto May 30 '11 at 09:58
  • I would ignore the first PMD warning. The second one, though, is justified. Your Pizza class, as it is in your question, is unusable since it doesn't have any method. Throw in some getters, and PMD won't complain anymore (hopefully). – JB Nizet May 30 '11 at 10:05

2 Answers2

2

About how to remove duplication.

I'll get more downvotes :) But maybe something like this?

class Pizza {
private int size;
private boolean cheese;
private boolean pepperoni;
private boolean bacon;

public static class Builder {
    private Pizza pizza = new Pizza();

    public Builder(int size) {
        pizza.size = size;
    }

    public Builder cheese(boolean value) {
        pizza.cheese = value;
        return this;
    }

    public Builder pepperoni(boolean value) {
        pizza.pepperoni = value;
        return this;
    }

    public Builder bacon(boolean value) {
        pizza.bacon = value;
        return this;
    }

    public Pizza build() {
        return pizza;
    }
}

private Pizza() {
}
}
weekens
  • 8,064
  • 6
  • 45
  • 62
  • 7
    I don't like this. You can change fields on your Pizza via the Builder that created it AFTER calling build(). One of the best uses for the builder pattern is to ease the creation of immutable classes. – Kenny Aug 08 '12 at 12:43
  • Sorry, but with this style you still also get: 1) "Avoid instantiation through private constructors from outside of the constructor's class." and 2) "Class cannot be instantiated and does not provide any static methods or fields", so the problems are not solved. – aloplop85 Aug 26 '13 at 08:25
1

the private fields in class Pizza and Builder are duplicate. This will be annoying when the number of private fields getting bigger. Is there any way to avoid it?

I personally get around this by using a third private static value object class which contains all the fields and use it both in the builder and main class (field access is handled by delegation). Sure, this might end up inflating the line count/class count but is invaluable in case your builders end up being complicated with a large number of fields and checks.

Also, it won't hurt to actually provide a static method on the Pizza class which builds a Pizza object with the mandatory fields. Unless of course you are not sure what the mandatory fields are or are afraid that the mandatory fields may change during the course of your class evolution. Point being, as long as you can justify your actions after a lot of thought (like Joshua Bloch puts it), you can safely ignore those warnings knowing that you know what you are doing. :-)

A throwaway snippet:

public class Pizza {

    private final PizzaVO vo;

    private static class PizzaVO {

        int size;

        boolean cheese;

        boolean pepperoni;

        boolean bacon;
    }

    public static class Builder {

        private final PizzaVO vo = new PizzaVO();

        public Builder(int size) {
            vo.size = size;
        }

        public Builder cheese(boolean value) {
            vo.cheese = value;
            return this;
        }

        public Builder pepperoni(boolean value) {
            vo.pepperoni = value;
            return this;
        }

        public Builder bacon(boolean value) {
            vo.bacon = value;
            return this;
        }

        public Pizza build() {
            return new Pizza(vo);
        }
    }

    private Pizza(PizzaVO vo) {
        this.vo = vo;
    }

    public int getSize() {
        return vo.size;
    }

    // other getter setter methods as per your taste

}
Sanjay T. Sharma
  • 22,857
  • 4
  • 59
  • 71