Pattern - Builder
먼저 개척해라. 그리고 고독해져라.
- 버지니아 로메니 ( IBM 회장겸 CEO )
의도
생성자의 인자가 많을 때는 Builder 패턴을 고려해야 합니다. 빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용합니다.
활용성
- Builder : Product 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의합니다.
- ConcreteBuilder : 클래스에 정의된 인터페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합합니다. 생성한 요소의 표현을 정의하고 관리합니다. 또한 제품을 검색하는데 필요한 인터페이스를 제공합니다.
- Director : Builder 인터페이스를 사용하는 객체를 합성합니다.
-
Product : 생성할 복합 객체를 표현합니다. ConcreteBuilder는 제품의 내부 표현을 구축하고 복합 객체가 어떻게 구성되는지에 관한 절차를 정의합니다.
- 빌더 패턴의 이익과 부담
- 제품에 대한 내부 표현을 다양하게 표현할 수 있습니다.
- 생성과 표현에 필요한 코드를 분리합니다. 빌더 패턴을 사용하면 복합 객체를 생성하고 복합 객체의 내부 표현 방법을 별도의 모듈로 정의할 수 있습니다.
- 복합 객체를 생성하는 절차를 좀더 세밀하게 나눌 수 있습니다.
- Effective Java - Builder Pattern
- GoF의 빌더 패턴보다 좀 더 코딩 위주의 활용법을 설명합니다.
- 코드 읽기/유지보수가 편해지므로 빌더 패턴을 쓰라고 합니다.
- GoF가 책을 썼을 때에는 상대적으로 덜 중요했던 객체 일관성, 변경 불가능성 등의 특징을 설명합니다.
1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성이 일시적으로 깨질 수 있다는 것, 자바빈 패턴으로는 변경 불가능 클래스를 만들 수 없다는 것, 점층적 생성자 패턴의 안정성에 자바빈 패턴의 가동성을 결합한 세 번째 대안이 있다는 것, 필요한 객체를 직접 생성하는 대신, 클라이언트는 먼저 필수 인자들을 생성자에 전부 전달하여 빌더 객체를 만듭니다. 그런 다음 빌더 객체에 정의된 설정 메서드들을 호출하여 선택적 인자들을 추가해 나갑니다. 그리고 아무런 인자 없이 build 메서드를 호출하여 변경 불가능 객체를 만드는 것입니다.
package DesignPattern.gof_builder.sampe03;
public class NutrionFacts {
private final int servingSize;
private final int servings;
private final int calories;
public static class Builder {
// 필수 인자
private final int servingSize;
private final int servings;
private int calories;
public Builder(int servingSize, int servings){
this.servingSize = servingSize;
this.servings =servings;
}
public Builder calories(int calories){
this.calories = calories;
return this;
}
public NutrionFacts Build(){
return new NutrionFacts(this);
}
}
private NutrionFacts(Builder builder){
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
}
}
코드 예제
Gof 빌더 패턴
객체의 생성 알고리즘과 조립 방법을 분리하는 것이 목적이다,
package DesignPattern.gof_builder.sample01;
public class DropShip {
private int dropshipCapacity = 8;
private int SeigeTankSize = 0;
private int MarineSize = 0;
private int MedicSize = 0;
private int FireBatSize = 0;
private int VultureSize = 0;
private int GhostSize = 0;
private int GoliathSize = 0;
private boolean SeigeTankOnBoard = false;
private boolean MarineOnBoard = false;
private boolean MedicOnBoard = false;
private boolean FireBatOnBoard = false;
private boolean VultureOnBoard = false;
private boolean GhostOnBoard = false;
private boolean GoliathOnBoard = false;
public static class Builder {
// 필수 값
private final int dropshipCapacity;
private int SeigeTankSize = 4;
private boolean SeigeTankOnBoard = false;
private int MarineSize = 1;
private boolean MarineOnBoard = false;
private int MedicSize = 1;
private boolean MedicOnBoard = false;
private int FireBatSize = 1;
private boolean FireBatOnBoard = false;
private int VultureSize = 2;
private boolean VultureOnBoard = false;
private int GhostSize = 1;
private boolean GhostOnBoard = false;
private int GoliathSize = 2;
private boolean GoliathOnBoard = false;
public Builder(int dropshipCapacity){
this.dropshipCapacity = dropshipCapacity;
}
public Builder SeigeTank(int SeigeTankCount){
SeigeTankSize = SeigeTankSize * SeigeTankCount;
SeigeTankOnBoard = true;
return this;
}
public Builder Marine(int MarineCount){
MarineSize = MarineSize * MarineCount;
MarineOnBoard = true;
return this;
}
public Builder Medic(int MedicCount){
MedicSize = MedicSize * MedicCount;
MedicOnBoard = true;
return this;
}
public Builder FireBat(int FireBatCount){
FireBatSize = FireBatSize * FireBatCount;
FireBatOnBoard = true;
return this;
}
public Builder Vulture(int VultureCount){
VultureSize = VultureSize * VultureCount;
VultureOnBoard = true;
return this;
}
public Builder Ghost(int GhostCount){
GhostSize = GhostSize * GhostCount;
GhostOnBoard = true;
return this;
}
public Builder Goliath(int GoliathCount){
GoliathSize = GoliathSize * GoliathCount;
GoliathOnBoard = true;
return this;
}
public DropShip onBoard(){
return new DropShip(this);
}
}
private DropShip(Builder builder){
dropshipCapacity = builder.dropshipCapacity;
SeigeTankSize = builder.SeigeTankSize;
MarineSize = builder.MarineSize;
MedicSize = builder.MedicSize;
FireBatSize = builder.FireBatSize;
VultureSize = builder.VultureSize;
GhostSize = builder.GhostSize;
GoliathSize = builder.GoliathSize;
SeigeTankOnBoard = builder.SeigeTankOnBoard;
MarineOnBoard = builder.MarineOnBoard;
MedicOnBoard = builder.MedicOnBoard;
FireBatOnBoard = builder.FireBatOnBoard;
VultureOnBoard = builder.VultureOnBoard;
GhostOnBoard = builder.GhostOnBoard;
GoliathOnBoard = builder.GoliathOnBoard;
}
public String checkOnBoardMember(){
String onBoardList = "";
if(SeigeTankOnBoard) onBoardList += " 시즈탱크 탑승 : " + SeigeTankSize + "\r\n";
if(MarineOnBoard) onBoardList += " 마린 탑승 : " + MarineSize + "\r\n";
if(MedicOnBoard) onBoardList += " 메딕 탑승 : " + MedicSize + "\r\n";
if(FireBatOnBoard) onBoardList += " 파이어벳 탑승 : " + FireBatSize + "\r\n";
if(VultureOnBoard) onBoardList += " 벌처 탑승 : " + VultureSize + "\r\n";
if(GhostOnBoard) onBoardList += " 고스트 탑승 : " + GhostSize + "\r\n";
if(GoliathOnBoard) onBoardList += " 골리앗 탑승 : " + GoliathSize + "\r\n";
return onBoardList;
}
}
package DesignPattern.gof_builder.sample01;
public class OnBoardMain {
public static void main(String[] args) {
DropShip.Builder onBoardOrder = new DropShip.Builder(8);
onBoardOrder.FireBat(1);
onBoardOrder.Ghost(1);
onBoardOrder.SeigeTank(1);
onBoardOrder.Goliath(1);
DropShip dropShip = onBoardOrder.onBoard();
System.out.printf(dropShip.checkOnBoardMember());
}
}
Lombok을 이용한 builder 패턴 구현
package DesignPattern.gof_builder.sample02;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class DropShip {
private int dropshipCapacity;
private int SeigeTankSize;
private int MarineSize;
private int MedicSize;
private int FireBatSize;
private int VultureSize;
private int GhostSize;
private int GoliathSize;
@Builder
public DropShip(int dropshipCapacity, int SeigeTankSize, int MarineSize, int MedicSize, int FireBatSize, int VultureSize, int GhostSize, int GoliathSize){
dropshipCapacity = dropshipCapacity;
SeigeTankSize = SeigeTankSize;
MarineSize = MarineSize;
MedicSize = MedicSize;
FireBatSize = FireBatSize;
VultureSize = VultureSize;
GhostSize = GhostSize;
GoliathSize = GoliathSize;
}
}
Kotlin - Builder
package bong.lines.patterns.builder.datas
data class Group(
var name: String,
var company: Company,
var members: List<Member>
)
data class Company(
var name : String = ""
)
data class Member (
var name : String,
val alias : String,
var year: Int
)
package bong.lines.patterns.builder
import bong.lines.patterns.builder.datas.Company
import bong.lines.patterns.builder.datas.Group
import bong.lines.patterns.builder.datas.Member
class MemberBuilder {
private var name: String = ""
private var alias: String = ""
private var year: Int = 0
fun name(lambda: () -> String) {
name = lambda()
}
fun alias(lambda: () -> String) {
alias = lambda()
}
fun year(lambda: () -> Int) {
year = lambda()
}
fun build() = Member(name, alias, year)
}
class MemberListBuilder {
private val employeeList = mutableListOf<Member>()
fun member(lambda: MemberBuilder.() -> Unit) =
employeeList.add(MemberBuilder().apply(lambda).build())
fun build() = employeeList
}
class CompanyBuilder {
private var name = ""
fun name(lambda: () -> String) {
this.name = lambda()
}
fun build() = Company(name)
}
class GroupBuilder {
private var name = ""
private var company = Company("")
private val employees = mutableListOf<Member>()
fun name(lambda: () -> String) {
name = lambda()
}
fun company(lambda: CompanyBuilder.() -> Unit) {
company = CompanyBuilder().apply(lambda).build()
}
fun members(lambda: MemberListBuilder.() -> Unit) =
employees.addAll(MemberListBuilder().apply(lambda).build())
fun build() = Group(name, company, employees)
}
fun group(lambda: GroupBuilder.() -> Unit): Group {
return GroupBuilder().apply(lambda).build()
}
package bong.lines.patterns.builder
val redVelvet =
group {
name { "레드벨벳" }
company {
name { "SM Entertainment" }
}
members {
member {
name { "슬기" }
alias { "곰슬기" }
year { 1994 }
}
member {
name { "아이린" }
alias { "얼굴 천재" }
year { 1991 }
}
member {
name { "웬디" }
alias { "천사" }
year { 1994 }
}
}
}
fun main() {
print("${redVelvet}")
}