일반적으로 복잡한 구조물을 세울 때 한 번에 완성시키기는 어려움
전체를 구성하고 있는 각 부분을 만들고 단계를 밟아 만들어나감
이런식으로 구조를 가진 인스턴스를 쌓아올리는 것이 Builder 패턴임
Builder의 경우 문서를 구성하기 위한 메소드를 결정하는 추상 클래스
Director의 경우 한 개의 문서를 만드는 클래스
TextBuilder는 일반 텍스트(보통의 문자열)를 이용해서 문서를 만드는 클래스
HTMLBuilder는 HTML 파일을 이용해서 문서를 만드는 클래스
여기서 Builder 클래스에서 문서를 구성하기 위한 메소드를 결정함, 추상 클래스이고 실제의 처리는 기술이 되어있지 않고 추상 메소드만 선언됨
그리고 Director 클래스가 그 메소드를 사용해서 구체적인 하나의 문서를 만듬
문서를 위한 구체적인 처리를 결정하는 것은 Builder 클래스의 하위 클래스임 즉, TextBuilder, HTMLBuilder가 각각 그 역할을 함
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();
}
makeTitle은 타이틀, makeString은 문자열, makeItems는 개별 항목을 문서 안에 구축하는 메소드임
close 메소드는 문서를 완성시키는 메소드임
public class Director {
private Builder builder;
public Director(Builder builder) { // Builder의 하위 클래스의 인스턴스가 주어지므로
this.builder = builder; // builder 필드에 저장해둠
}
public void construct() { // 문서구축
builder.makeTitle("Greeting"); // 타이틀
builder.makeString("아침과 낮에"); // 문자열
builder.makeItems(new String[] { // 개별 항목
"좋은 아침입니다.",
"안녕하세요.",
});
builder.makeString("밤에"); // 별도의 문자열
builder.makeItems(new String[] { // 별도의 개별 항목
"안녕하세요.",
"안녕히 주무세요.",
"안녕히 계세요.",
});
builder.close(); // 문서를 완성시킨다.
}
}
Director 클래스는 Builder 클래스로 선언되어 있는 메소드를 사용해서 문서를 만듬
Director 클래스의 생성자의 인수는 Builder형임, 실제로 Builder 클래스의 인스턴스는 주어ㅓ지는 경우는 없음, 왜냐면 Builder 클래스는 추상클래스이므로
그래서 Director의 생성자에게 실제로 전달되는 것은 Builder의 하위 클래스(TextBuilder나 HTMLBuilder)의 인스턴스임
주어진 Builder 클래스의 하위 클래스 종류에 따라 Director 클래스가 만들 구체적인 문서의 형식이 정해짐
construct 메소드는 문서를 만드는 메소드인데, Builder에서 선언되어 있는 메소드만을 사용함, 이 메소드를 호출하면 문서가 만들어짐
public class TextBuilder extends Builder {
private StringBuilder buffer = new StringBuffer(); // 필드의 문서를 구축함
public void makeTitle(String title) { // 일반 텍스트의 제목
buffer.append("=======================\n"); // 장식선
buffer.append("『" + title + "』\n"); // 『 』사용한 타이틀
buffer.append("\n"); // 빈 행
}
public void makeString(String str) { // 일반 텍스트의 문자열
buffer.append("■" + str + "\n"); // ■ 글머리 기호 붙은 문자열
buffer.append("\n"); // 빈 행
}
public void makeItems(String[] items) { // 일반 텍스트에서의 개별항목
for (int i = 0; i < items.length; i++) {
buffer.append("ㆍ" + items[i] + "\n"); // ㆍ글머리 기호 붙은 항목
}
buffer.append("\n"); // 빈 행
}
public void close() { // 문서의 완성
buffer.append("=====================\n"); // 장식선
}
public String getResult() { // 완성한 문서
return buffer.toString(); // StringBuffer를 String으로 변환
}
}
import java.io.*;
public class HTMLBuilder extends Builder {
private String filename; // 작성할 파일명
private PrintWriter writer; // 파일에 기술할 PrintWriter
public void makeTitle(String title) { // HTML 파일에서의 타이틀
filename = title + ".html"; // 타이틀을 기초로 파일명을 결정
try {
writer = new PrintWriter(new FileWriter(filename)); // PrintWriter을 만듬
} catch (IOException e) {
e.printStackTrace();
}
writer.println("<html><head><title>" + title + "</title></head><body>"); // 타이틀 출력
writer.println("<h1>" + title + "</h1>");
}
public void makeString(String str) { // HTML 파일에서의 문자열
writer.println("<p>" + str + "</p>"); // <p> 태그로 출력
}
public void makeItems(String[] items) { // HTML 파일에서의 개별항목
writer.println("<ul>"); // <ul>과 <li>로 출력
for (int i = 0; i < items.length; i++) {
writer.println("<li>" + items[i] + "</li>");
}
writer.println("</ul>");
}
public void close() { // 문서의 완성
writer.println("</body></html>"); // 태그를 닫는다
writer.close(); // 파일을 닫는다
}
public String getResult() { // 완성한 문서
return filename; // 파일명을 반환함
}
}
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
usage();
System.exit(0);
}
if (args[0].equals("plain")) {
TextBuilder textbuilder = new TextBuilder();
Director director = new Director(textbuilder);
director.construct();
String result = textbuilder.getResult();
System.out.println(result);
} else if (args[0].equals("html")) {
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director = new Director(htmlbuilder);
director.construct();
String filename = htmlbuilder.getResult();
System.out.println(filename + "가 작성되었습니다.");
} else {
usage();
System.exit(0);
}
}
public static void usage() {
System.out.println("Usage: java Main plain 일반 텍스트로 문서작성");
System.out.println("Usage: java Main html HTML 파일로 문서작성");
}
}
커맨드 라인에서 plain을 지정한 경우에는 TextBuilder 클래스의 인스턴스를 Director 클래스의 생성자에게 전달하고 html을 지정한 경우에는 HTMLBuilder 클래스의 인스턴스를 Director 클래스의 생성자에게 전달함
Builder의 메소드만을 사용한다는 것은 Director는 실제로 동작하는 것이 TextBuilder인지, HTMLBuilder인지 모른다는 것임
그래서 Builder에서는 해당 목적을 달성하기 위해서 필요 충분한 메소드군을 선언해야함 단, Text파일이나 HTML 파일에 고유의 메소드까지 제공해선 안됨
Builder의 경우 인스턴스를 생성하기 위한 인터페이스(API)를 결정함, 인스턴스의 각 부분을 만들기 위한 메소드가 준비되어 있음
ConcreteBuilder의 경우 Builder 역할의 인터페이스(API)를 구현하고 있는 클래스임, 실제의 인스턴스 작성으로 호출되는 메소드가 여기에서 정의됨, 그리고 최종적인 결과를 얻기 위한 메소드가 준비되어 있음
Director의 경우 Builder 역할의 인터페이스(API)를 사용해서 인스턴스를 생성함, ConcreteBuilder 역할에 의존한 프로그래밍은 수행하지 않음, ConcreteBuilder 역할과 관계없이 제대로 기능하도록 Builder 역할의 메소드만을 사용함
Client의 경우 Builder 패턴을 이용함
정리하자면 Director 클래스의 경우 Builder 클래스의 메소드를 사용해서 문서를 구축하는데 이때 자신이 실제로 이용하고 있는 클래스가 무엇인지 모름
그저 Builder 클래스의 메소드만을 사용하고 있고 Builder 클래스의 하위 클래스는 그 메소드를 구현하기 때문임, 하지만 이렇게 모르기 때문에 교체할 수 있음, 모르기 때문에 교환이 가능하고 교체가 가능하기 때문에 부품으로서 가치가 높음
Builder 패턴을 통해서 결국 객체의 생성을 간소화한다고 볼 수 있음, 그러면 같은 생성 과정에서 다른 요소를 가지고 같은 클래스의 객체를 만들 수 있는 것임
이를 바탕으로 안드로이드에서 Builder 패턴을 사용하는 것을 적지않아 볼 수 있음
그 중에서 AlertDialog.Builder
를 본다면 코드는 아래와 같음
AlertDialog.Builder(this)
.setTitle("Sandwich Dialog")
.setMessage("Please use the spicy mustard.")
.setNegativeButton("No thanks") { dialogInterface, i ->
// "No thanks" action
}
.setPositiveButton("OK") { dialogInterface, i ->
// "OK" action
}
.show()
이를 통해서 Builder의 과정을 통해서 필요한 요소를 메소드 호출로써 AlertDialog
의 필요로 한 특정 부분만을 구체화 하고 있음, 그리고 간단하게 AlertDialog를 생성 가능함
이 부분이 바로 위에서 Builder패턴에서 봤던 장점과 요소들이 들어가 있는 것임, 이 AlertDialog
를 만드는 사용자는 공식 Docs나 내부 코드를 보지 않으면 구체적으로 어떻게 쓰여서 메소드가 AlertDialog
를 만드는지 정확히 모름, 하지만 해당 단순히 메소드만을 사용해서 AlertDialog
를 원하는대로 구성하는 것임
이 AlertDialog 외에도 안드로이드 개발하는데 있어서 라이브러리 사용시 이런 Builder 패턴을 사용하는 방식을 흔히 접함, 이 상황에서 우린 내부적인 것을 잘 모르더라도 그 사용법에 맞게 해당 라이브러리와 기술들을 편하게 활용할 수 있음