Search code examples
javagenericsinheritancedesign-patternsbuilder-pattern

Implementation of Hierarchical Builder pattern


I am implementing Hierarchical Builder Pattern. I have used generics to make return type of my Parent Builder setter as that for child. Still I am facing problem when I use the child setter method after calling parent setter method. I don't want to define my child specific method (setEngine(String) here) in my Parent Builder. Is there any other way around for this problem?

I have made an example snippet for the mentioned problem, identical to this case.

CarFactory -> It returns the object of specific car that user wants

Car -> Parent for all Car types, Swift, Duster etc

Swift -> Specific car

Parent->Child Hierarchy

Car -> Swift

CarBuilder -> SwiftBuilder

Car.java

 package Builders;

 public class Car {
   int tyre;
   int seat;
   public int getTyre() {
       return tyre;
   }
   public void setTyre(int tyre) {
       this.tyre = tyre;
   }
   public int getSeat() {
       return seat;
   }
   public void setSeat(int seat) {
       this.seat = seat;
       
   }
   
}

Swift.java

package Builders;

public class Swift extends Car {
    boolean safetyAirbag;
    String engine;
    
    public boolean isSafetyAirbag() {
        return safetyAirbag;
    }

    public String getEngine() {
        return engine;
    }

    public void setSafetyAirbag(boolean safetyAirbag) {
        this.safetyAirbag = safetyAirbag;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }


}

CarBuilder.java

package Builders;

public abstract class CarBuilder {
        int tyre;
        int seat;
         
        public abstract <B extends CarBuilder>B self();
        public abstract <T extends Car>T typeOfCar();
            
        public <B extends CarBuilder>B setTyre(int tyre) {
            this.tyre = tyre;
            return self();
        }
        
        public  <B extends CarBuilder> B setSeat(int seat) {
            this.seat = seat;
            return self();
        }
        public <C extends Car>C build()
        {   C car=this.typeOfCar();
            car.setSeat(seat);
            car.setTyre(tyre);
            return car;
        }
    
        
}

SwiftBuilder.java

package Builders;

public class SwiftBuilder extends CarBuilder {
    String engine;
    @Override
    public
    SwiftBuilder self() {
        
        return this;
    }

    @Override
    public
    Swift typeOfCar() {
        
        return new Swift();
    }

    public SwiftBuilder setEngine(String string) {
        this.engine=string;
        return this;
    }
    public Swift build()
    {   Swift s=(Swift)super.build();
        return s;
    }

}

CarFactory.java

package Builders;

public class CarFactory {
    public SwiftBuilder getSwiftDesire()
    {
        return new SwiftBuilder();
    }
}
 

Drivers.java

package Builders;

public class Drivers {
    Swift getMyCar() {
        Swift s= this.factory().getSwiftDesire().setSeat(4).setEngine("CC").build();
        return s;
    }
    CarFactory factory() {
        return new CarFactory();
    }
}

In Drivers.java class I am not able to use setEngine() method after setSeat() method, this.factory().getSwiftDesire().setSeat(4).setEngine("CC").build(); I don't want to declare setEngine in parent class, is there any way around for same?

Thank you in advance!


Solution

  • You need to use generics on the class level, not on the method level, in your CarBuilder:

    package Builders;
    
    public abstract class CarBuilder<B extends CarBuilder<B, C>, C extends Car> {
        int tyre;
        int seat;
    
        public abstract B self();
    
        public abstract C typeOfCar();
    
        public B setTyre(int tyre) {
            this.tyre = tyre;
            return self();
        }
    
        public B setSeat(int seat) {
            this.seat = seat;
            return self();
        }
    
        public C build() {
            C car = this.typeOfCar();
            car.setSeat(seat);
            car.setTyre(tyre);
            return car;
        }
    
    }
    

    And then you define your SwiftBuilder:

    package Builders;
    
    public class SwiftBuilder extends CarBuilder<SwiftBuilder, Swift> {
        String engine;
    
        @Override
        public SwiftBuilder self() {
    
            return this;
        }
    
        @Override
        public Swift typeOfCar() {
    
            return new Swift();
        }
    
        public SwiftBuilder setEngine(String string) {
            this.engine = string;
            return this;
        }
    
        public Swift build() {
            Swift s = super.build();
            s.setEngine(engine);
            return s;
        }
    
    }
    

    And it works.