Builder (Budowniczy)

Wyobraźmy sobie, że chcemy stworzyć klasę reprezentującą dom. Dom może składać się z różnych elementów, takich jak ściany, podłogi, pokoje, dach, okna, drzwi, czy garaż.

Najprostszym rozwiązaniem jest dodanie wszystkich tych pól do klasy i stworzenie kilku konstruktorów, które pozwolą zbudować obiekt House z różną liczbą parametrów.

Takie podejście działa, ale szybko staje się problematyczne:

  • konstruktor z wieloma argumentami jest trudny do czytania i łatwo pomylić kolejność parametrów,

  • konieczne jest tworzenie wielu przeciążonych konstruktorów dla różnych kombinacji,

  • kod staje się mniej elastyczny i trudniejszy w utrzymaniu.

Poniżej przedstawiam przykład kodu bez użycia wzorca projektowego Budowniczy. 

Klasa House:

package house;

public class House {
    private String walls;
    private String floors;
    private String rooms;
    private String roof;
    private String windows;
    private String doors;
    private String garage;

    public House(String walls, String floors, String rooms, String roof, String windows, String doors, String garage) {
        this.walls = walls;
        this.floors = floors;
        this.rooms = rooms;
        this.roof = roof;
        this.windows = windows;
        this.doors = doors;
        this.garage = garage;
    }

    public House(String roof, String walls, String floors) {
        this.roof = roof;
        this.walls = walls;
        this.floors = floors;
    }

    public House(String walls, String floors, String rooms, String roof, String windows, String doors) {
        this.walls = walls;
        this.floors = floors;
        this.rooms = rooms;
        this.roof = roof;
        this.windows = windows;
        this.doors = doors;
    }

    public String getWalls() {
        return walls;
    }

    public String getFloors() {
        return floors;
    }

    public String getRooms() {
        return rooms;
    }

    public String getRoof() {
        return roof;
    }

    public String getWindows() {
        return windows;
    }

    public String getDoors() {
        return doors;
    }

    public String getGarage() {
        return garage;
    }

    public void setWalls(String walls) {
        this.walls = walls;
    }

    public void setFloors(String floors) {
        this.floors = floors;
    }

    public void setRooms(String rooms) {
        this.rooms = rooms;
    }

    public void setRoof(String roof) {
        this.roof = roof;
    }

    public void setWindows(String windows) {
        this.windows = windows;
    }

    public void setDoors(String doors) {
        this.doors = doors;
    }

    public void setGarage(String garage) {
        this.garage = garage;
    }

    @Override
    public String toString() {
        return "House{" +
                "walls='" + walls + '\'' +
                ", floors='" + floors + '\'' +
                ", rooms='" + rooms + '\'' +
                ", roof='" + roof + '\'' +
                ", windows='" + windows + '\'' +
                ", doors='" + doors + '\'' +
                ", garage='" + garage + '\'' +
                '}';
    }
}

Następnie, zwróć uwagę na klasę Demo:

import house.House;

public class Demo {
    public static void main(String args[]){
        House house1 = new House("walls", "floors", "rooms","windows", "doors", "garage");
    }
}

W klasie House, tych konstruktorów może być znacznie więcej, przez co stworzy się bałagan w kodzie i będziemy mieć problem ze wskazaniem w klasie głównej, do jakich i ilu parametrów konstruktora mamy się odwołać przy wywoływaniu obiektów house. Tutaj z pomocą przychodzi właśnie wzorzec Builder.

Wzorzec ten możemy podzielić na dwa rodzaje.

Pierwszy do którego się odniosę, to budowniczy z klasą wewnętrzną. Spójrzmy na poniższy kod:

package house;

public class House {
    private String walls;
    private String floors;
    private String rooms;
    private String roof;
    private String windows;
    private String doors;
    private String garage;


    private House(HouseBuilder houseBuilder){
        this.walls = houseBuilder.walls;
        this.floors = houseBuilder.floors;
        this.rooms = houseBuilder.rooms;
        this.roof = houseBuilder.roof;
        this.doors = houseBuilder.doors;
        this.windows = houseBuilder.windows;
        this.garage = houseBuilder.garage;
    }

    public String getWalls() {
        return walls;
    }

    public String getFloors() {
        return floors;
    }

    public String getRooms() {
        return rooms;
    }

    public String getRoof() {
        return roof;
    }

    public String getWindows() {
        return windows;
    }

    public String getDoors() {
        return doors;
    }

    public String getGarage() {
        return garage;
    }

    @Override
    public String toString() {
        return "House{" +
                "walls='" + walls + '\'' +
                ", floors='" + floors + '\'' +
                ", rooms='" + rooms + '\'' +
                ", roof='" + roof + '\'' +
                ", windows='" + windows + '\'' +
                ", doors='" + doors + '\'' +
                ", garage='" + garage + '\'' +
                '}';
    }

    public static class HouseBuilder{
        private String walls;
        private String floors;
        private String rooms;
        private String roof;
        private String windows;
        private String doors;
        private String garage;

        public HouseBuilder buildWalls(String walls){
            this.walls = walls;
            return this;
        }

        public HouseBuilder buildFloors(String floors){
            this.floors = floors;
            return this;
        }

        public HouseBuilder buildRooms(String rooms){
            this.rooms = rooms;
            return this;
        }

        public HouseBuilder buildRoof(String roof){
            this.roof = roof;
            return this;
        }

        public HouseBuilder buildWindows(String windows){
            this.windows = windows;
            return this;
        }

        public HouseBuilder buildDoors(String doors){
            this.doors = doors;
            return this;
        }

        public HouseBuilder buildGarage(String garage){
            this.garage = garage;
            return this;
        }

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

W  klasie Demo możemy teraz odwołać się do dowolnych funkcji “budujących” wspomniany dom. Możemy na przykład pominąć funkcję dodającą garaż:

import house.House;

public class Demo {
    public static void main(String args[]){
 //       House house1 = new House("walls", "floors", "rooms","windows", "doors", "garage");

        House house = new House.HouseBuilder()
                .buildWalls("walls")
                .buildFloors("floors")
                .buildRoof("roof")
                .buildRooms("rooms")
                .buildWindows("windows")
                .buildDoors("doors")
                .build();

        System.out.println(house);

    }
}

Kolejny rodzaj wzorca – budowniczy klasyczny.

Klasa House:

package house;

public class House {
    private String walls;
    private String floors;
    private String rooms;
    private String roof;
    private String windows;
    private String doors;
    private String garage;

    public String getWalls() {
        return walls;
    }

    public String getFloors() {
        return floors;
    }

    public String getRooms() {
        return rooms;
    }

    public String getRoof() {
        return roof;
    }

    public String getWindows() {
        return windows;
    }

    public String getDoors() {
        return doors;
    }

    public String getGarage() {
        return garage;
    }

    public void setWalls(String walls) {
        this.walls = walls;
    }

    public void setFloors(String floors) {
        this.floors = floors;
    }

    public void setRooms(String rooms) {
        this.rooms = rooms;
    }

    public void setRoof(String roof) {
        this.roof = roof;
    }

    public void setWindows(String windows) {
        this.windows = windows;
    }

    public void setDoors(String doors) {
        this.doors = doors;
    }

    public void setGarage(String garage) {
        this.garage = garage;
    }

    @Override
    public String toString() {
        return "House{" +
                "walls='" + walls + '\'' +
                ", floors='" + floors + '\'' +
                ", rooms='" + rooms + '\'' +
                ", roof='" + roof + '\'' +
                ", windows='" + windows + '\'' +
                ", doors='" + doors + '\'' +
                ", garage='" + garage + '\'' +
                '}';
    }
}

Następnie, tworzymy interfejs HouseBuilder:

package house;

public interface HouseBuilder {
    void buildDoors();
    void buildFloors();
    void buildWalls();
    void buildRooms();
    void buildRoof();
    void buildWindows();
    void buildGarage();

    House getHouse();
}

Następnie, tworzymy klasę-dyrektora:

package house;

public class HouseDirector {
    private HouseBuilder houseBuilder;

    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public void buildHouse(){
        houseBuilder.buildWalls();
        houseBuilder.buildFloors();
        houseBuilder.buildRooms();
        houseBuilder.buildRoof();
        houseBuilder.buildWindows();
        houseBuilder.buildDoors();
        houseBuilder.buildGarage();
    }

    public House getHouse(){
        return this.houseBuilder.getHouse();
    }
}

Następnie, tworzymy klaśe implementującą interfejs HouseBuilder. Ja utworzę dwie przykładowe klasy. 

Pierwsza SmallHouseBuilder:

package house;

public class SmallHouseBuilder implements HouseBuilder{
    private House house;

    public SmallHouseBuilder() {
        this.house = new House();
    }

    @Override
    public void buildDoors() {
        this.house.setDoors("small doors");
    }

    @Override
    public void buildFloors() {
        this.house.setFloors("small floors");
    }

    @Override
    public void buildWalls() {
        this.house.setWalls("small walls");
    }

    @Override
    public void buildRooms() {
        this.house.setRooms("small rooms");
    }

    @Override
    public void buildRoof() {
        this.house.setRoof("small roof");
    }

    @Override
    public void buildWindows() {
        this.house.setWindows("small windows");
    }

    @Override
    public void buildGarage() {
        this.house.setGarage("small garage");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

Drugas klasa to BigHouseBuilder:

package house;

public class BigHouseBuilder implements HouseBuilder{
    private House house;

    public BigHouseBuilder() {
        this.house = new House();
    }

    @Override
    public void buildDoors() {
        this.house.setDoors("big doors");
    }

    @Override
    public void buildFloors() {
        this.house.setFloors("big floors");
    }

    @Override
    public void buildWalls() {
        this.house.setWalls("big walls");
    }

    @Override
    public void buildRooms() {
        this.house.setRooms("big rooms");
    }

    @Override
    public void buildRoof() {
        this.house.setRoof("big roof");
    }

    @Override
    public void buildWindows() {
        this.house.setWindows("big windows");
    }

    @Override
    public void buildGarage() {
        this.house.setGarage("big garage");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

Na koniec, implkementujemy klasę główną:

import house.BigHouseBuilder;
import house.House;
import house.HouseDirector;
import house.SmallHouseBuilder;

public class Demo {
    public static void main(String args[]){
         SmallHouseBuilder smallHouseBuilder = new SmallHouseBuilder();
         BigHouseBuilder bigHouseBuilder = new BigHouseBuilder();

         HouseDirector smallHouseDirector = new HouseDirector(smallHouseBuilder);
         smallHouseDirector.buildHouse();

         HouseDirector bigHouseDirector = new HouseDirector(bigHouseBuilder);
         bigHouseDirector.buildHouse();

         House smallHouse = smallHouseDirector.getHouse();
         House bigHouse = bigHouseDirector.getHouse();

        System.out.println(smallHouse);
        System.out.println(bigHouse);
    }
}

Przewijanie do góry