7. Builder 패턴
개요
건물을 건축할 때 여러 단계를 거쳐가면 건물을 지어가게 되는데, 이와 같이 여러 과정을 거쳐서 인스턴스를 쌓아올리는 패턴을 Builder 패턴이라 한다.
Builder 패턴의 과정을 다른 비유로 보면 총과 총알이다. 이 총(Director)은 크기만 같다면 여러가지 총알을 넣어서 사용할 수 있다. 총알을 넣고 방아쇠를 당기기만 하면 된다. 총알(Builder)은 다양한 종류(ConcreteBuilder)가 있는데 모양은 동일(Builder를 상속)하다. 사용자는 총의 내부 구조나 총알의 내부는 몰라도 된다. 그저 총에 원하는 효과의 총알을 넣고 방아쇠를 당기기만 하면 되는 것이다.
사용자가 Director에 원하는 효과를 가진 ConcereteBuilder 인스턴스를 넣어서 Director의 방아쇠를 당기기만 하면 되는 것이다.
역할
Builder 역할
인스턴스를 생성하기 위한 메서드를 정의한다. 예제에서는 Builder 클래스
ConcreteBuilder 역할
Builder에서 정의한 메서드를 구현한다. 예제에서는 TextBuilder와 HTMLBuilder 클래스.
Director 역할
Builder 역할에서 정의한 메서드를 이용해 인스턴스를 생성한다. 외부에서 주입된 ConcreteBuilder 인스턴스를 이용해서 인스턴스를 생성한다. 예제에서 Director 클래스.
Client 역할
Builder 패턴을 실행한다. 예제에서 Main BuilderTester클래스
이득 및 유의사항
사용자는 Director에 메서드를 실행하면 원하는 작업이 실행된다. 사용자는 Director 메서드 내부가 어떤지 몰라도 되고 Builder에 어떤 메서드가 있는지 몰라도 상관이 없다.
한편 Director는 Builder에서 정의된 메서드를 이용해서 작업을 하는데, 실제 인스턴스를 생성하는 ConcreteBuilder가 어떤 클래스인지, 메서드는 어떻게 구현되어 있는지 몰라도 상관이 없다.
결과적으로 사용자는 그저 Director의 메서드만 알면 내부가 어떻게 바뀌고 교체되는지 신경쓰지 않아도 되고, Direcotr도 ConcreteBuilder가 어떤 인스턴스가 주어지든 상관하지 않아도 되기 때문에 교체가 가능하다.
유의해야 할 점은 Builder 클래스에 작업에 필요한 충분한 메서드를 정의해놓아야 한다는 점이다. Director는 Builder에서 정의된 메서드만을 사용하기 때문에 작업이 다양해지면서 ConcreteBuilder의 기능이 추가되더라도 Builder에 이에 대응하는 메서드를 정의하고 있어야 하는 것이다.
예제 코드
//Builder
package 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();
}
//ConcreteBuilder1
package builder;
public class TextBuilder extends Builder{
private StringBuffer sb = new StringBuffer();
@Override
public void makeTitle(String title) {
// TODO Auto-generated method stub
sb.append("=======================\n");
sb.append("-"+title+"-\n");
sb.append("\n");
}
@Override
public void makeString(String str) {
// TODO Auto-generated method stub
sb.append('~' + str +"\n");
sb.append("\n");
}
@Override
public void makeItems(String[] items) {
// TODO Auto-generated method stub
for(int i=0; i<items.length; i++) {
sb.append(" *"+items[i]+"\n");
}
sb.append("\n");
}
@Override
public void close() {
// TODO Auto-generated method stub
sb.append("=======================\n");
}
public String getResult() {
return sb.toString();
}
}
//ConcreteBuilder2
package builder;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class HTMLBuilder extends Builder{
private String filename;
private PrintWriter pw;
@Override
public void makeTitle(String title) {
// TODO Auto-generated method stub
filename = title+".html";
try {
pw = new PrintWriter(new FileWriter(filename));
} catch(IOException e) {
e.printStackTrace();
}
pw.println("<html><head><title>"+title+"</title></head><body>");
pw.println("<h1>"+title+"</h1>");
}
@Override
public void makeString(String str) {
// TODO Auto-generated method stub
pw.println("<p>"+str+"</p>");
}
@Override
public void makeItems(String[] items) {
// TODO Auto-generated method stub
pw.println("<ul>");
for(int i=0; i<items.length; i++) {
pw.println("<li>"+items[i]+"</li>");
}
pw.println("</ul>");
}
@Override
public void close() {
// TODO Auto-generated method stub
pw.println("</body></html>");
}
public String getResult() {
return filename;
}
}
//Director
package builder;
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[] {
"좋은 아침"
,"언뇽허새요"
,
});
builder.makeString("밤에");
builder.makeItems(new String[] {
"안녕하세여"
,"안녕히 주무세여"
,"안녕히 계세여"
,
});
}
}
//Client
package builder;
public class BuilderTester {
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 파일로 문서작성");
}
}