전체를 구성하고 있는 각 부분을 만들고 단계를 밟아 만들어 나가는 패턴. 구조를 가진 인스턴스를 쌓아 올리는 패턴.
"각 부분을 만들고 단계를 밟아 만들어 나가는 패턴" 이 말이 가장 중요하다. 그래서 다양한 오픈소스를 보더라도, 각 부분을 만들고 단계를 밟는 형태이면 전부 Builder라고 명시하는 듯 하다.
public abstract class Builder {
public abstract void makeTitle(String title);
public abstract void makeString(String str);
public abstract void makeItems(String[] items);
public abstract void close();
}
public class Director {
private Builder builder;
// 생성자
public Director(Builder builder) {
this.builder = builder;
}
// 문서를 만드는 메소드
public void construct() {
builder.makeTitle("Greeting");
builder.makeString("일반적인 인사");
builder.makeItems(new String[]{
"How are you?",
"Hello.",
"Hi.",
});
builder.makeString("시간대별 인사");
builder.makeItems(new String[]{
"Good morning.",
"Good afternoon.",
"Good evening.",
});
builder.close();
}
}
public class HTMLBuilder extends Builder {
private String filename = "untitled.html";
private StringBuilder sb = new StringBuilder();
@Override
public void makeTitle(String title) {
filename = title + ".html";
sb.append("<!DOCTYPE html>\n");
sb.append("<html>\n");
sb.append("<head><title>");
sb.append(title);
sb.append("</title></head>\n");
sb.append("<body>\n");
sb.append("<h1>");
sb.append(title);
sb.append("</h1>\n\n");
}
@Override
public void makeString(String str) {
sb.append("<p>");
sb.append(str);
sb.append("</p>\n\n");
}
@Override
public void makeItems(String[] items) {
sb.append("<ul>\n");
for (String s: items) {
sb.append("<li>");
sb.append(s);
sb.append("</li>\n");
}
sb.append("</ul>\n\n");
}
@Override
public void close() {
sb.append("</body>");
sb.append("</html>\n");
try {
Writer writer = new FileWriter(filename);
writer.write(sb.toString());
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public String getHTMLResult() {
return filename;
}
}
public static void main(String[] args) {
TextBuilder textbuilder = new TextBuilder();
Director director1 = new Director(textbuilder);
director1.construct();
String result = textbuilder.getTextResult();
System.out.println(result);
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director2 = new Director(htmlbuilder);
director2.construct();
String filename = htmlbuilder.getHTMLResult();
System.out.println("HTML파일 " + filename + "이 작성되었습니다.");
}
Director는 실제로 어떤 인자가 사용되는지 알 수가 없다.
Builder (건축자)의 역할 : Builder 역할은 인스턴스를 생성하기 위한 API 결정. Builder 역할에서는 인스턴스가 만들어야할 메소드들을 명세. 예제에서는 Builder가 이 역할을 한다.
ConcreteBuilder (구체적인 건축가)의 역할 : 실제의 인스턴스 작성으로 호출되는 메소드가 여기에 정의된다. 또한 실제로 결과를 얻어내는 부분을 여기에 명세한다.
Director (감독자)의 역할 : Director 역할은 Builder 역할의 메소드 만을 사용하여 Builder를 상속한 클래스들의 내부 인스턴스의 값들을 생성해낸다. 예제 프로그램에서는 Director가 이 역할을 한다.
Client(의뢰인)의 역할 : Builder 패턴을 사용하는 역할이고, Main 클래스이다.
Main 클래스는 Builder 클래스의 내부 정보를 전혀 알지 못하기 때문에, Main에서는 단순히 교체가 가능하다.
위 예제에서는 빌더를 활용하여 다형성을 사용할 수 있음을 알리는 것 같다.
이러한 빌더 이외에도 객체를 구성하는 요소들을 단계별로 처리하는 방식을 inner로 구현이 가능하다.
public class Computer {
//required parameters
private String HDD;
private String RAM;
//optional parameters
private boolean isGraphicsCardEnabled;
private boolean isBluetoothEnabled;
public String getHDD() {
return HDD;
}
public String getRAM() {
return RAM;
}
public boolean isGraphicsCardEnabled() {
return isGraphicsCardEnabled;
}
public boolean isBluetoothEnabled() {
return isBluetoothEnabled;
}
private Computer(ComputerBuilder builder) {
this.HDD=builder.HDD;
this.RAM=builder.RAM;
this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
this.isBluetoothEnabled=builder.isBluetoothEnabled;
}
//Builder Class
public static class ComputerBuilder{
// required parameters
private String HDD;
private String RAM;
// optional parameters
private boolean isGraphicsCardEnabled;
private boolean isBluetoothEnabled;
public ComputerBuilder(String hdd, String ram){
this.HDD=hdd;
this.RAM=ram;
}
public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
this.isGraphicsCardEnabled = isGraphicsCardEnabled;
return this;
}
public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
this.isBluetoothEnabled = isBluetoothEnabled;
return this;
}
public Computer build(){
return new Computer(this);
}
}
}
클래스 레벨 어노테이션
@Builder
public class BuildMe {
private String username;
private int age;
public BuildMe(String username) {
this.username = username;
this.age = 1;
}
}
생성자 레벨 어노테이션
public class BuildMe {
private String username;
private int age;
@Builder
public BuildMe(String username) {
this.username = "Mr/Mrs. " + username;
this.age = 1;
}
}
기본적으로 builder 패턴을 사용할 때 default 값들을 지정하기 좋은 방식이다.
반드시 초기화되어야 하는 필드의 경우 lombok의 @Builder.Default 속성을 사용하거나 선언 시점에 또는 생성자에서 초기화하는 것이 좋다.
@Singular("alias")
@Singular 어노테이션을 사용하면 리스트 같은 컬렉션 객체를 빌더 패턴으로 다룰 때 리스트 객체 자체를 넘기는 게 아니라 해당 리스트에 요소를 추가하는 방식으로 생성할 수 있다.
XxxString.builder()
.alias("String")