- natalia.kaczynska.programista@gmail.com
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);
}
}