Builder 패턴

gang_shik·2022년 3월 11일
0

Builder 패턴

  • 일반적으로 복잡한 구조물을 세울 때 한 번에 완성시키기는 어려움

  • 전체를 구성하고 있는 각 부분을 만들고 단계를 밟아 만들어나감

  • 이런식으로 구조를 가진 인스턴스를 쌓아올리는 것이 Builder 패턴임


예제 프로그램

  • Builder의 경우 문서를 구성하기 위한 메소드를 결정하는 추상 클래스

  • Director의 경우 한 개의 문서를 만드는 클래스

  • TextBuilder는 일반 텍스트(보통의 문자열)를 이용해서 문서를 만드는 클래스

  • HTMLBuilder는 HTML 파일을 이용해서 문서를 만드는 클래스

  • 여기서 Builder 클래스에서 문서를 구성하기 위한 메소드를 결정함, 추상 클래스이고 실제의 처리는 기술이 되어있지 않고 추상 메소드만 선언됨

  • 그리고 Director 클래스가 그 메소드를 사용해서 구체적인 하나의 문서를 만듬

  • 문서를 위한 구체적인 처리를 결정하는 것은 Builder 클래스의 하위 클래스임 즉, TextBuilder, HTMLBuilder가 각각 그 역할을 함

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();
}
  • makeTitle은 타이틀, makeString은 문자열, makeItems는 개별 항목을 문서 안에 구축하는 메소드임

  • close 메소드는 문서를 완성시키는 메소드임

Director 클래스

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에서 선언되어 있는 메소드만을 사용함, 이 메소드를 호출하면 문서가 만들어짐

TextBuilder 클래스

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으로 변환
		}
}
  • TextBuilder는 Builder 클래스의 하위 클래스임, 일반 텍스트를 사용해서 문서를 구축하고 결과를 String으로 반환함

HTMLBuilder 클래스

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; // 파일명을 반환함
		}
}
  • HTMLBuilder 클래스도 Builder 클래스의 하위 클래스임, HTML 파일로 문서를 구축함, 구축한 결과는 HTML 파일의 파일명으로 반환함

Main 클래스

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 패턴을 사용하는 방식을 흔히 접함, 이 상황에서 우린 내부적인 것을 잘 모르더라도 그 사용법에 맞게 해당 라이브러리와 기술들을 편하게 활용할 수 있음

profile
측정할 수 없으면 관리할 수 없고, 관리할 수 없으면 개선시킬 수도 없다

0개의 댓글