GoF - Builder 패턴

June 30, 2022

Builder Pattern

빌더 패턴은 복잡한 구조의 객체의 표현과 생성을 분리한다. 그리고 생성 과정을 캡슐화하여 동일한 인스턴스 생성 프로세스를 사용하여, 다양한 구성을 할 수 있도록 하는 패턴이다.

예를 들어, 아래와 같이 휴가를 나타내는 객체가 존재한다고 하자. 휴가는 일련의 이벤트와 숙박 정보로 이루어지며, 숙박 정보는 내부에 예약에 대한 정보를 가진다.

public class Vacation {
	String name;
	List<Accommodation> accommodations = new ArrayList<Accommodation>();
	List<String> events = new ArrayList<String>();
 
	public void setName(String name) {
		this.name = name;
	}

	public void setAccommodations(List<Accommodation> accommodations) {
		this.accommodations = accommodations;
	}

	public void setEvents(List<String> events) {
		this.events = events;
	}

	public String toString() {
		StringBuffer display = new StringBuffer();
		display.append("---- " + this.name + " ----\n");

		for (Accommodation a : accommodations) {
			display.append(a);
		}
		for (String e : events) {
			display.append(e + "\n");
		}

		return display.toString();
	}
}
public abstract class Accommodation {
	String name;
	Reservation reservation = null;
	
	public void setReservation(Reservation r) {
		this.reservation = r;
	}

	public Reservation getReservation() {
		return this.reservation;
	}

	public abstract String getLocation();

	public String toString() {
		StringBuffer display = new StringBuffer();
		display.append("You're staying at " + name);
		
		if (this.reservation != null) {
			display.append("\nYou have a reservation for arrival date: " + reservation.getArrivalDate() + 
					", staying for " + reservation.getNights() + " nights");
		}
		
		if (this.getLocation() != "") {
			display.append(" in " + this.getLocation());
		}
		display.append("\n");
		
		return display.toString();
	}
}

---------------------------------------------

public class Tent extends Accommodation {
	int siteNumber;

	public Tent() {
		this.name = "Tent";
	}

	public Tent(String name) {
		this.name = name;
	}

	public void setSiteNumber(int n) {
		this.siteNumber = n;
	}

	public int getSiteNumber() {
		return this.siteNumber;
	}

	public String getLocation() {
		if (siteNumber == 0) return "";
		else return "Site number " + this.siteNumber;
	}
}

---------------------------------------------

public class Hotel extends Accommodation {
	int roomNumber;

	public Hotel() {
		this.name = "Hotel";
	}

	public Hotel(String name) {
		this.name = name;
	}

	public void setRoomNumber(int n) {
		this.roomNumber = n;
	}

	public int getRoomNumber() {
		return this.roomNumber;
	}

	public String getLocation() {
		if (roomNumber == 0) return "";
		else return "Room number " + this.roomNumber;
	}
}
public class Reservation {
	LocalDate arrivalDate;
	int nights;
	
	public void setArrivalDate(int year, int month, int day) {
		this.arrivalDate = LocalDate.of(year, month, day);
	}

	public LocalDate getArrivalDate() {
		return this.arrivalDate;
	}

	public void setNights(int nights) {
		this.nights = nights;
	}

	public int getNights() {
		return this.nights;
	}
}

클라이언트에서 이 휴가를 나타내는 인스턴스를 생성하려면 어떻게 해야할까? 위 코드만 존재한다면 new 키워드를 통해 인스턴스를 생성하고 필요한 모든 내용들을 일일히 setter를 통해 세팅해줘야 한다.

하지만 이 방법은 객체를 생성하기 위해 클라이언트가 모든 구조를 알고 있다는 전제가 있어야 한다. 만약 필수 값이 존재한다거나, 값에 대한 검증, 또 생성을 하는데 있어 순서 제어가 필요한 경우 이를 클라이언트에 모두 위임할 수 밖에 없다. 또한 객체를 생성하기 위해서 너무 많은 메서드를 호출해야 되며, 모든 값을 설정하기 전에는 객체가 일관성이 무너진 상태에 놓이게 된다는 점 또한 문제가 된다.

빌더 패턴에서는 이를 해결하기 위해 객체의 생성을 담당하는 빌더 클래스를 제공하여 클라이언트에서 일관된 방법으로 인스턴스를 생성할 수 있도록 한다.

패턴 적용

public abstract class VacationBuilder {
	String name;
	List<Accommodation> accommodations = new ArrayList<Accommodation>();
	List<String> events = new ArrayList<String>();
	
	public abstract VacationBuilder addAccommodation();
	public abstract VacationBuilder addAccommodation(String name);
	public abstract VacationBuilder addAccommodation(String name, int year, int month, int day, int nights, int location);
	public abstract VacationBuilder addEvent(String event);
	
	public Vacation getVacation() {
		Vacation vacation = new Vacation();
		vacation.setName(name);
		vacation.setAccommodations(accommodations);
		vacation.setEvents(events);
		return vacation;
	}
}

---------------------------------------------

public class OutdoorsVacationBuilder extends VacationBuilder {	
	public OutdoorsVacationBuilder() {
		this.name = "Outdoorsy Vacation Builder";
	}

	public VacationBuilder addAccommodation() {
		this.accommodations.add(new Tent());
		return this;
	}

	public VacationBuilder addAccommodation(String name) {
		this.accommodations.add(new Tent(name));
		return this;
	}

	public VacationBuilder addAccommodation(String name, int year, int month, int day, int nights, int location) {
		Reservation reservation = new Reservation();
		reservation.setArrivalDate(year, month, day);
		reservation.setNights(nights);
		
		Tent tent = new Tent(name);
		tent.setReservation(reservation);
		tent.setSiteNumber(location);
		this.accommodations.add(tent);
		return this;
	}

	public VacationBuilder addEvent(String event) {
		this.events.add("Hike: " + event);
		return this;
	}
}

---------------------------------------------

public class CityVacationBuilder extends VacationBuilder {	
	public CityVacationBuilder() {
		this.name = "City Vacation Builder";
	}

	public VacationBuilder addAccommodation() {
		this.accommodations.add(new Hotel());
		return this;
	}

	public VacationBuilder addAccommodation(String name) {
		this.accommodations.add(new Hotel(name));
		return this;
	}

	public VacationBuilder addAccommodation(String name, int year, int month, int day, int nights, int location) {
		Reservation reservation = new Reservation();
		reservation.setArrivalDate(year, month, day);
		reservation.setNights(nights);
		
		Hotel hotel = new Hotel(name);
		hotel.setReservation(reservation);
		hotel.setRoomNumber(location);
		this.accommodations.add(hotel);
		return this;
	}

	public VacationBuilder addEvent(String event) {
		this.events.add("See the " + event + " show");
		return this;
	}
}
VacationBuilder outdoorsyVacationBuilder = new OutdoorsVacationBuilder();
Vacation outdoorsyVacation = outdoorsyVacationBuilder
		.addAccommodation("Two person tent", 2020, 7, 1, 5, 34)
		.addEvent("Beach")
		.addAccommodation("Two person tent")
		.addEvent("Mountains")
		.getVacation();
System.out.println(outdoorsyVacation);

VacationBuilder cityVacationBuilder = new CityVacationBuilder();
Vacation cityVacation = cityVacationBuilder
		.addAccommodation("Grand Facadian", 2020, 8, 1, 5, 0)
		.addAccommodation("Hotel Commander", 2020, 8, 6, 2, 0)
		.addEvent("Cirque du Soleil")
		.getVacation();
System.out.println(cityVacation);

참고


songmk 🙁