[Design Pattern] Builder Pattern 빌더패턴

이은수, Lee EunSoo·2024년 10월 9일
0

DesignPattern

목록 보기
8/12
post-thumbnail

개요

빌더 패턴이란?

프로그램의 기능과 구현을 분리하여 둘의 구조가 독립적으로 변화될 수 있도록 한 패턴

복합체의 구축 과정에서 그 형식을 분리 하여 동일한 구축방식으로 다양한 종류의 복합체를 만들 수 있도록 한 형태
→ 한 객체의 내부 구축과정을 외부의 존재가 하도록 함.

DI, 의존성 주입도 이용한다.

설명

형태

역할

  • Builder
    • 복합체를 구현하기 위해 사용할 인터페이스를 선언한다.
      (또는 추상클래스로 선언한다.)
  • ConcreateBuilder
    • Builder인터페이스를 구현한다.
      • getResult() → role method
  • Director
    • 복합체를 구축하는 방법을 알고 있음
  • Client
    • Builder패턴을 이용하는 존재, 보통 Main클래스

특징

  1. Bridge패턴의 특징은 기능 계층과 구현 계층을 분리하여 각각을 독립적으로 확장할 수 있다는 것
    1. 기능의 추가는 기능 계층에서만 이루어 지고 구현계층에는 영향을 미치지 않는다.
  2. 상속은 강한결합, 위임은 약한 결합
    1. 상속을 사용하면 클래스 간의 관계가 고정되어 소스코드를 다시 쓰지 않는 한 변경할 수 없다.
    2. 위임을 사용한다면 교체가 용이하다.
  3. builder를 구현하는 클래스에는 생성자가 별도로 정의 되지 않는다.
    1. 객체의 완성을 외부에서 하기 때문

적용 시점

1. 프로그램 실행시 특정한 implementation이 선택되도록 할 때
2. Abstraction과 implementation이 별도의 계층구조를 가질 때
3. implementation부분을 수정하여도 client코드에는 영향이 없을 때

구조

  • 2가지 형태의 클래스 계층
    • 기능 계층
      • 새로운 기능을 추가하기 위한 계층
    • 구현 계층
      • 상위 클래스의 인터페이스와 하위 클래스에서 구현해야 할 때 생기는 계층
  • 2종류의 클래스 계층이 혼합되면 나타나는 문제점
    • client코드가 플랫폼 의존적이 된다.

DI, 의존석 주입

의존성 주입에 해당되는 형태가 적용된다.

의존성 주입
→ 참조변수를 설정하기 위해서 외부에서 생성된 인스턴스를 생성자나 setter에 전달하는것

TextBuilder tb = new TextBuilder();
Director director = new Director(tb);

예제 코드

구조

Director와 Builder클래스는 이름과 역할이 동일하다.

TextBuilder와 HTMLBuilder의 경우 ConcreateBuilder의 역할이다.

Main은 Client의 역할을 수행한다.

Java

import java.io.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;


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();
}

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();
    }
}

class TextBuilder extends Builder {
    private StringBuilder sb = new StringBuilder();

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

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

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

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

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

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 class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }

        //arg에 어떤 text가 있는지에 따라 다른 객체를 생성하는 방식의 main문
        if (args[0].equals("text")) {
            TextBuilder textbuilder = new TextBuilder();
            System.out.println(textbuilder.getTextResult());
            Director director = new Director(textbuilder);
            director.construct();
            String result = textbuilder.getTextResult();
            System.out.println(result);
        } else if (args[0].equals("html")) {
            HTMLBuilder htmlbuilder = new HTMLBuilder();
            Director director = new Director(htmlbuilder);
            director.construct();
            String filename = htmlbuilder.getHTMLResult();
            System.out.println("HTML파일 " + filename + "이 작성되었습니다.");
        } else {
            //만약 args[]에 아무 데이터가 없을 경우 안내 문구를 출력
            usage();
            System.exit(0);
        }
    }

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

Swift

import Foundation

protocol Builder {
    func makeTitle(_ title: String)
    func makeString(_ str: String)
    func makeItems(_ items: [String])
    func close()
}
class Director {
    var builder: Builder
    
    init(_ builder: Builder) {
        self.builder = builder
    }
    
    func construct() {
        builder.makeTitle("Greeting")
        builder.makeString("일반적인 인사")
        builder.makeItems(["How are you?", "Hello.", "Hi."])
        builder.makeString("시간대별 인사")
        builder.makeItems(["Good morning.", "Good afternoon.", "Good evening."])
        builder.close()
    }
}
class TextBuilder : Builder{
    //swift에서는 StringBuilder를 사용안해도 일반 String이 java의 StringBuilder처럼 동작함
    private var sb: String = ""
    
    init(){
        
    }
    
    func makeTitle(_ title: String) {
        sb.append("=======================\n")
        sb.append("[ \(title) ]\n\n")
    }
    func makeString(_ str: String) {
        sb.append(" ■ \(str)\n\n")
    }
    func makeItems(_ items: [String]) {
        for s in items {
            sb.append(" - ");
            sb.append(s);
            sb.append("\n");
        }
        sb.append("\n");
    }
    
    func close() {
        sb.append("==============================\n")
    }
    func getTextResult() -> String {
        return sb
    }
}


class HTMLBuilder: Builder {
    private var filename: String = "untitled.html"
    private var sb: String = ""
    
    func makeTitle(_ title: String) {
        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")
    }
    
    func makeString(_ str: String) {
        sb.append("<p>")
        sb.append(str)
        sb.append("</p>\n\n")
    }
    
    func makeItems(_ items: [String]) {
        sb.append("<ul>\n")
        for s in items {
            sb.append("<li>")
            sb.append(s)
            sb.append("</li>\n")
        }
        sb.append("</ul>\n\n")
    }
    func close() {
        sb.append("</body>")
        sb.append("</html>\n")
        //파일 출력
        
        // 파일 이름과 경로 지정 (여기서는 Document 디렉토리에 저장)
        
        if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
            let fileURL = documentDirectory.appendingPathComponent(filename)
            
            do {
                // 문자열을 데이터로 변환
                try sb.write(to: fileURL, atomically: true, encoding: .utf8)
                print("File successfully written to: \(fileURL.path)")
            } catch {
                print("Failed to write to file: \(error.localizedDescription)")
            }
        }
    }
    
    func getHTMLResult() -> String {
        return filename
    }
}

@main
struct Main {
    static func main() {
        print("출력하고자 하는 형태를 입력하세요(text or html): ")
        let input = readLine() ?? ""
        print(input)
        
        if (input == "text") {
            let textbuilder: TextBuilder = TextBuilder();
            print(textbuilder.getTextResult());
            let director: Director = Director(textbuilder);
            director.construct();
            let result: String = textbuilder.getTextResult();
            print(result);
        } else if (input == "html") {
            let htmlbuilder: HTMLBuilder = HTMLBuilder();
            let director: Director = Director(htmlbuilder);
            director.construct();
            let filename: String = htmlbuilder.getHTMLResult();
            print("HTML파일 \(filename)이 작성되었습니다.");
        } else {
            //만약 args[]에 아무 데이터가 없을 경우 안내 문구를 출력
            usage();
            
        }
    }
    static func usage() {
        print("Usage: java Main text       텍스트로 문서 작성");
        print("Usage: java Main html       HTML 파일로 문서 작성");
    }
}

정리

프로그램의 기능과 구현을 분리하여 둘의 구조가 독립적으로 변화될 수 있도록 한 패턴이다.

기능 계층과 구현 계층을 분리하여 각각을 독립적으로 확장할 수 있다

기능의 추가는 기능 계층에서만 이루어 지고 구현계층에는 영향을 미치지 않는다.

profile
iOS 개발자 취준생, 천 리 길도 한 걸음부터

0개의 댓글