Builder模式:首选哪种变体?

时间:2013-03-17 13:10:20

标签: java effective-java builder-pattern

我正在阅读Effective Java书籍,并为我将来的参考创建笔记, 我遇到了Builder Pattern。

我明白它是什么以及如何使用它。在这个过程中,我创建了两个构建模式的变体。

我需要帮助列出每个人的差异和优势吗? 好吧,我当然注意到,Example 1暴露的方法较少,而且限制性较小 更通用的,允许它更灵活地使用。

请指出我错过的其他事情?

示例1

package item2;

/**
 * @author Sudhakar Duraiswamy
 *
 */
public  class Vehicle {

    private String type;
    private int wheels;

    interface Builder<T>{
        public  T build();
    }

    public static class CarBuilder implements Builder<Vehicle>{
        private String type;
        private int wheels;     

        CarBuilder createVehicle(){
            this.type= "Car";
            return this;
        }

        CarBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

        public Vehicle build(){
            Vehicle v = new Vehicle();
            v.type = type;
            v.wheels = wheels;
            return v;
        }               
    }

    public static class TruckBuilder implements Builder<Vehicle>{       
        private String type;
        private int wheels; 

        TruckBuilder createVehicle(){           
            this.type= "Truck";
            return this;
        }

        TruckBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

        public Vehicle build(){
            Vehicle v = new Vehicle();
            v.type = type;
            v.wheels = wheels;
            return v;
        }
    }   

    public Vehicle(){

    }

    public static void main(String[] args) {
        //This builds a car with 4 wheels
        Vehicle car = new Vehicle.CarBuilder().createVehicle().addWheels(4).build();

        //THis builds a Truck with 10 wheels
        Vehicle truck = new Vehicle.TruckBuilder().createVehicle().addWheels(10).build();

    }
}

示例2

package item2;
/**
 * @author Sudhakar Duraiswamy
 *
 */
public  class Vehicle2 {

    private String type;
    private int wheels;

    interface Builder<T>{
        public  T build();      
        public String getType();
        public int getWheels() ;
    }

    public static class CarBuilder implements Builder<Vehicle2>{
        private String type;
        private int wheels;     

        public String getType() {
            return type;
        }
        public int getWheels() {
            return wheels;
        }

        CarBuilder createVehicle(){
            this.type= "Car";
            return this;
        }

        CarBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

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

    public static class TruckBuilder implements Builder<Vehicle2>{      
        private String type;
        private int wheels; 

        public String getType() {
            return type;
        }

        public int getWheels() {
            return wheels;
        }

        TruckBuilder createVehicle(){           
            this.type= "Truck";
            return this;
        }

        TruckBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

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


public Vehicle2(Builder<? extends Vehicle2> builder){
    Vehicle2 v = new Vehicle2();
    v.type = builder.getType();
    v.wheels = builder.getWheels();
}

    public Vehicle2(){
    }

    public static void main(String[] args) {            
        //This builds a car with 4 wheels
        Vehicle2 car = new Vehicle2.CarBuilder().createVehicle().addWheels(4).build();

        //THis builds a Truck with 10 wheels
        Vehicle2 truck = new Vehicle2.TruckBuilder().createVehicle().addWheels(10).build();
    }
}

2 个答案:

答案 0 :(得分:8)

以上都不是。

第一个不允许构建不可变的Vehicle,这通常是使用Builder模式的原因。

第二个示例是第一个示例的变体,它允许使用其他getter方法从构建器获取信息。但是那些方法不会在任何地方使用,除了在Vehicle构造函数中,它可以直接访问构建器字段。我没有看到添加它们的重点。

我认为有两件更重要的事情需要改进:

  1. 两种构建器类型完全相同。不需要两种类型。一个就足够了。
  2. createVehicle()方法应该由构建器构造函数完成。如果你构建一个CarBuilder,显然是要建造一辆汽车,所以一旦构建了构建器,就应该设置车辆的类型。这是我写它的方式:
  3. public final class Vehicle {
    
        private final String type;
        private final int wheels;
    
        private Vehicle(Builder builder) {
            this.type = builder.type;
            this.wheels = builder.wheels;
        }
    
        public static Builder carBuilder() {
            return new Builder("car");
        }
    
        public static Builder truckBuilder() {
            return new Builder("truck");
        }
    
        public static class Builder {
            private final String type;
            private int wheels;
    
            private Builder(String type) {
                this.type = type;
            }
    
            public Builder addWheels(int wheels){
                this.wheels = wheels;
                return this;
            }
    
            public Vehicle build() {
                return new Vehicle(this);
            }               
        }
    
        public static void main(String[] args) {
            Vehicle car = Vehicle.carBuilder().addWheels(4).build();
            Vehicle truck = Vehicle.truckBuilder().addWheels(10).build();
        }
    }
    

答案 1 :(得分:2)

还有第三种变体,代码较少:

构建器也可以改变Vehicle的状态,而不是拥有自己的实例字段。内部类可以编写其外部类的私有成员:

class Vehicle {
  private int wheels;

  private Vehicle() {}

  public static class Builder {
    private boolean building = true;
    private Vehicle vehicle = new Vehicle();

    public Builder buildWheels(int wheels) {
      if(!this.building) throw new IllegalStateException();
      this.vehicle.wheels = wheels;
      return this;
    }

    public Vehicle build() {
      this.building = false;
      return this.vehicle;
    }
  }
}

由于这些字段是私有的,并且您只允许一次构建building标志),因此构建的Vehicle实例仍然是不可变的对于消费者来说,即使字段不再是final(C#上不再有realio-trulio immutability, see Eric's blog article,但概念类似)。

您需要更加小心,因为在构造对象期间不需要初始化非最终字段(由编译器强制执行),您必须仔细检查building状态。但是,您可以保存所有实例字段的完整额外副本。一般来说,如果你有一组相当大的实例变量,这些变量是用很少的方法构建的,每个方法一次构建几个字段,这很有用。

我知道这并未指出您的方法的任何优点或缺点。但是,如果您不需要将字段设置为final,则此方法可以节省大量额外代码。