나를 기록하다
article thumbnail
반응형

JAVA 언어로 배우는 디자인 패턴 입문

Builder 패턴

일반적으로 구조를 갖춘 큰 구조물을 건축하거나 구축하는 것을 build라고 하고, 구조를 갖춘 커다란 건축물을 빌딩(building)이라고 한다. 빌딩을 지을 때는 먼저 지반을 다진 후, 뼈대를 만들고 아래에서 위로 조금씩 만들어 간다. 대체로 복잡한 구조를 가진 구조물을 만들 경우, 단숨에 완성하기는 어렵다. 우선 전체를 구성하는 각 부분을 만들고 단계를 밟아가며 만들게 된다. 이러한 구조를 가진 인스턴스를 만들어 가는 Builder 패턴에 대해 알아보자.


예제 프로그램

Builder 패턴을 사용해 '문서'를 작성하는 프로그램

문서는 다음과 같은 구조로 되어 있다.

  • 타이틀을 한 개 포함한다
  • 문자열을 몇 개 포함한다
  • 항목을 몇 개 포함한다

Builder 클래스에서는 문서를 구성하는 메서드를 결정한다. 그리고 Director 클래스는 그 메서드를 이용해서 구체적인 하나의 문서를 만든다. Builder는 추상 클래스로 실제 처리는 작성되지 않고 추상 메서드만 선언되어 있다. 문서 작성을 위한 구체적인 처리를 결정하는 것은 Builder 클래스의 하위 클래스이다. 예제 클래스에서 Builder 클래스의 하위 클래스는 다음과 같이 정의하였다.

  • TextBuilder - 텍스트를 이용해서 문서를 만든다
  • HTMLBuilder - HTML을 이용해서 문서를 만든다

Director가 TextBuilder를 이용하면 일반 텍스트 문서, HTMLBuilder를 사용하면 HTML 문서가 만들어진다.


클래스 목록

이름 설명
Builder 문서를 구성하기 위한 메서드를 규정한 추상 클래스
Director 하나의 문서를 만드는 클래스
TexxtBuilder 텍스트를 이용하여 문서를 만드는 클래스
HTMLBuilder HTML 파일을 이용하여 문서를 만드는 클래스
Main 동작 테스트용 클래스

클래스 다이어그램

클래스 다이어그램


Builder 클래스

  • 문서를 만드는 메서드들을 선언한 추상 클래스
package ch07_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();
}

Director 클래스

  • Builder 클래스에서 선언된 메서드로 문서를 만드는 클래스
  • Director 클래스 생성자 인수는 Builder형이다. 실제로는 Builder 클래스의 하위 클래스의 인스턴스가 전달된다.
  • construct 메서드는 문서를 만드는 메서드이다.
package ch07_Builder;

public class Director {

    private Builder builder;

    //생성자

    public Director(final Builder builder) {
        this.builder = builder;
    }

    //문서를 만드는 메서드
    public void construct() {
        builder.makeTitle("프로그래밍 언어");
        builder.makeString("언어의 종류");
        builder.makeItems(new String[]{
                "Java",
                "Python",
                "C",
                "JavaScript",
                "Go",
                "Kotlin"
        });
        builder.makeString("프레임워크의 종류");
        builder.makeItems(new String[]{
                "Spring",
                "ReactNative",
                "Django",
                "Unity"
        });
        builder.close();
    }
}

TextBuilder 클래스

  • Builder 클래스의 하위 클래스
  • 결과는 String으로 반환
package ch07_Builder;

public class TextBuilder extends Builder{

    private StringBuilder sb = new StringBuilder();

    @Override
    public void makeTitle(final String title) {
        sb.append("=================================");
        sb.append("[");
        sb.append(title);
        sb.append("]\n\n");
    }

    @Override
    public void makeString(final String str) {
        sb.append("◼︎");
        sb.append(str);
        sb.append("\n\n");
    }

    @Override
    public void makeItems(final String[] items) {
        for (String item : items) {
            sb.append(" ・");
            sb.append(item);
            sb.append("\n");
        }
        sb.append("\n");
    }

    @Override
    public void close() {
        sb.append("================================");
    }

    public String getTextResult() {
        return sb.toString();
    }
}

HTMLBuilder 클래스

  • Builder 클래스의 하위 클래스
  • 결과는 HTML 파일의 파일명으로 반환
package ch07_Builder;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class HTMLBuilder extends Builder{

    private String filename = "untitled.html";
    private StringBuilder sb = new StringBuilder();

    @Override
    public void makeTitle(final 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(final String str) {
        sb.append("<p>");
        sb.append(str);
        sb.append("</p>\n\n");
    }

    @Override
    public void makeItems(final String[] items) {
        sb.append("<ul>\n");
        for (String item : items) {
            sb.append("<li>");
            sb.append(item);
            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;
    }
}

Main 클래스

  • Builder 패턴을 테스트하는 프로그램
  • 다음과 같이 커맨드 라인에서 지정한 형식에 따라 문서 작성
    • java Main text - 텍스트 문서 만들기
    • java Main html - HTML 문서 만들기
  • 커맨드 라인에서 text 지정 시 TextBuilder 클래스의 인스턴스를 Director 클래스의 생성자에게 전달
  • 커맨드 라인에서 html 지정 시 HTMLBuilder 클래스의 인스턴스를 Director 클래스의 생성자에게 전달
  • Director는 Builder의 메서드만 사용하여 문서를 만든다.
    • Builder의 메서드만을 사용한다는 것은 Director는 실제로 동작하는 것이 TextBuilder인지 HTMLBuilder인지 의식하지 않는다는 것을 의미한다.
    • 따라서 Builder는 문서 구축이라는 목적을 달성하는 데 필요하고 충분한 메서드군을 선언하고 있어야 한다.
    • 단 텍스트나 HTML 파일에 고유한 메서드까지 Builder가 제공해서는 안된다.
package ch07_Builder;

public class Main {

    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }
        if (args[0].equals("text")) {
            TextBuilder textBuilder = new TextBuilder();
            Director director = new Director(textBuilder);
            director.construct();
            String result = textBuilder.getTextResult();
            System.out.println(result);
        }
        if (args[0].equals("html")) {
            HTMLBuilder htmlBuilder = new HTMLBuilder();
            Director director = new Director(htmlBuilder);
            director.construct();
            String filename = htmlBuilder.getHTMLResult();
            System.out.println(filename);
            System.out.println("HTML 파일 " + filename + "이 작성되었습니다.");
        }
        if (!args[0].equals("text") && args[0].equals("html")) {
            usage();
            System.exit(0);
        }

    }


    //사용 방법 표시
    public static void usage() {
        System.out.println("Usage: java Main text       텍스트로 문서 작성");
        System.out.println("Usage: java Main html       HTML 파일로 문서 작성");
    }
}

 

실행결과

  • 커맨드 라인에 text 지정
=================================
[프로그래밍 언어]

◼︎언어의 종류

 ・Java
 ・Python
 ・C
 ・JavaScript
 ・Go
 ・Kotlin

◼︎프레임워크의 종류

 ・Spring
 ・ReactNative
 ・Django
 ・Unity

================================
  • 커맨드 라인에 html 지정
프로그래밍 언어.html
HTML 파일 프로그래밍 언어.html이 작성되었습니다.
  • 만들어진 HTML 파일(프로그래밍 언어.html)
<!DOCTYPE HTML>
<html>
<head><title>프로그래밍 언어</title></head>
<body>
<h1>프로그래밍 언어</h1>

<p>언어의 종류</p>

<ul>
<li>Java</li>
<li>Python</li>
<li>C</li>
<li>JavaScript</li>
<li>Go</li>
<li>Kotlin</li>
</ul>

<p>프레임워크의 종류</p>

<ul>
<li>Spring</li>
<li>ReactNative</li>
<li>Django</li>
<li>Unity</li>
</ul>

</body></html>
  • 커맨드 라인에 text나 html이 아닌 다른 값 지정
Usage: java Main text       텍스트로 문서 작성
Usage: java Main html       HTML 파일로 문서 작성

Builder 패턴의 다이어그램

역할

  • Builder
    • 인스턴스를 생성하기 위한 인터페이스(API) 결정
    • 인스턴스 각 부분을 만드는 메서드 보유
  • ConcreteBuilder
    • Builder의 인터페이스(API) 구현
    • 실제 인스턴스 생성으로 호출되는 메서드가 여기서 정의
    • 최종적으로 완성된 결과를 얻는 메서드 보유
  • Director
    • Builder의 인터페이스(API)를 구현하는 클래스
    • ConcreteBuilder가 무엇이든 잘 작동하도록 Builder의 메서드만을 사용
  • Client
    • Builder 패턴 이용(GoF 책에서는 Client가 Builder 패턴 안에 포함되지 않음)

클래스 다이어그램

Builder 패턴의 클래스 다이어그램


시퀀스 다이어그램

Builder 패턴의 시퀀스 다이어그램


관련 패턴

  • Template Method 패턴
    • Builder 패턴에서는 Director가 Builder 제어
    • Template Method 패턴에서는 상위 클래스가 하위 클래스 제어
  • Composite 패턴
    • Builder 패턴으로 만들어진 생성물은 Composite 패턴이 되는 경우가 존재
  • Abstract Factory 패턴
    • Builder 패턴과 Abstract Factory 패턴 모두 복잡한 인스턴스 생성
  • Facade 패턴
    • Builder 패턴의 Director는 Builder의 복잡한 메서드를 조합하여 인스턴스를 구축하는 간단한 인터페이스(API)를 외부에 제공
    • Facade 패턴의 Facade는 내부 모듈을 조합하여, 작업하기 위한 간단한 인터페이스(API)를 외부에 제공

누가 무엇을 알고 있는가?

객체지향 프로그래밍에서는 '누가 무엇을 알고 있는가?'가 매우 중요하다. 즉, 어느 클래스가 어느 메서드를 사용할 수 있는지에 주의하여 프로그래밍하여야 한다.

 

예제 프로그램에서 Main 클래스는 Builder 클래스를 모른다. Main 클래스는 Director 클래스의 construct 메서드만 호출한다. 그러면 Director 클래스 안에서 일이 진행되고 문서가 완성된다.

 

한편 Director 클래스가 알고 있는 것은 Builder 클래스이다. Director 클래스는 Builder 클래스의 메서드를 이용하여 문서를 구축한다. 하지만 Director 클래스는 자신이 실제로 이용하는 클래스가 사실은 무엇인지 모른다(TextBuilder인지 HTMLBuilder인지 아니면 Builder의 다른 하위 클래스인지). Director 클래스는 Builder 클래스의 메서드만을 사용하고 Builder 클래스의 하위 클래스는 그 메서드를 구현하고 있다.

 

Director 클래스가 자신이 이용하는 Builder 클래스의 하위 클래스를 모르기 때문에 교체를 할 수 있다.

모르기 때문에 교체가 가능하고, 교체되기 때문에 부품으로서의 가치가 높다. 이 '교체 가능성'을 클래스 설계자는 항상 생각해야 한다.


의존성 주입(DI; Dependency Injection)

Director 클래스는 Builder 클래스를 알고 있지만, Builder 클래스의 하위 클래스에 대해서 모른다. 결국, Director는 TextBuilder나 HTMLBuilder에 의존하지 않는다고 할 수 있다.

 

하지만 Director 클래스가 실제로 동작하려면 Builder 클래스의 구체적인 인스턴스가 필요하다. 그래서 Director 클래스의 생성자를 호출할 때 TextBuilder나 HTMLBuilder의 인스턴스를 인수로 전달한다. Director 클래스의 소스 코드에는 TextBuilder나 HTMLBuilder라고 쓰여져 있지 않지만, TextBuilder나 HTMLBuilder의 인스턴스에 의존해서 동작하게 된다.

 

'소스 코드에는 쓰여져 있지 않지만, 실제로는 이 인스턴스를 이용해(의존해) 동작해 주세요'라는 의미를 담아 인스턴스를 건네는 방법을 일반적으로 의존성 주입이라고 한다. 이는 클래스간 결합도를 낮추고 프로그램의 재사용성을 높여 주는 유용한 기법이다.


설계 시 결정할 수 있는 것과 결정할 수 없는 것

Builder 클래스는 문서를 구축하기 위해(목적을 달성하기 위해) 필요하고 충분한 메서드를 선언해야 한다. Director 클래스에 주어지는 도구는 Builder 클래스가 제공하므로, Builder 클래스의 메서드로 무엇을 준비하는지는 중요하다.

 

게다가 Builder 클래스는 앞으로 늘어날지도 모르는 Builder 클래스의 하위 클래스들의 요구에도 대응할 필요가 있다. 클래스 설계자가 미래에 일어날 모든 일을 예상할 수는 없지만 가까운 미래에 일어날 것으로 예상되는 변화에는 대응할 수 있도록 설계해야 한다.

 

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...