디자인패턴 (Builder)

백종현·2023년 4월 28일
0

Builder 패턴

전체를 구성하고 있는 각 부분을 만들고 단계를 밟아 만들어 나가는 패턴. 구조를 가진 인스턴스를 쌓아 올리는 패턴.

"각 부분을 만들고 단계를 밟아 만들어 나가는 패턴" 이 말이 가장 중요하다. 그래서 다양한 오픈소스를 보더라도, 각 부분을 만들고 단계를 밟는 형태이면 전부 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();
}
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[]{
            "How are you?",
            "Hello.",
            "Hi.",
        });
        builder.makeString("시간대별 인사");
        builder.makeItems(new String[]{
            "Good morning.",
            "Good afternoon.",
            "Good evening.",
        });
        builder.close();
    }
}
public 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 static void main(String[] args) {
    TextBuilder textbuilder = new TextBuilder();
    Director director1 = new Director(textbuilder);
    director1.construct();
    String result = textbuilder.getTextResult();
    System.out.println(result);
    
    HTMLBuilder htmlbuilder = new HTMLBuilder();
    Director director2 = new Director(htmlbuilder);
    director2.construct();
    String filename = htmlbuilder.getHTMLResult();
    System.out.println("HTML파일 " + filename + "이 작성되었습니다.");
}

Director는 실제로 어떤 인자가 사용되는지 알 수가 없다.

Iterator 패턴의 요소

Builder (건축자)의 역할 : Builder 역할은 인스턴스를 생성하기 위한 API 결정. Builder 역할에서는 인스턴스가 만들어야할 메소드들을 명세. 예제에서는 Builder가 이 역할을 한다.
ConcreteBuilder (구체적인 건축가)의 역할 : 실제의 인스턴스 작성으로 호출되는 메소드가 여기에 정의된다. 또한 실제로 결과를 얻어내는 부분을 여기에 명세한다.
Director (감독자)의 역할 : Director 역할은 Builder 역할의 메소드 만을 사용하여 Builder를 상속한 클래스들의 내부 인스턴스의 값들을 생성해낸다. 예제 프로그램에서는 Director가 이 역할을 한다.
Client(의뢰인)의 역할 : Builder 패턴을 사용하는 역할이고, Main 클래스이다.

왜 Builder를 사용할까?

Main 클래스는 Builder 클래스의 내부 정보를 전혀 알지 못하기 때문에, Main에서는 단순히 교체가 가능하다.

위 예제에서는 빌더를 활용하여 다형성을 사용할 수 있음을 알리는 것 같다.
이러한 빌더 이외에도 객체를 구성하는 요소들을 단계별로 처리하는 방식을 inner로 구현이 가능하다.

평소에 사용하던 inner 클래스를 활용한 Builder 패턴

public class Computer {
	
    //required parameters
    private String HDD;
    private String RAM;
	
    //optional parameters
    private boolean isGraphicsCardEnabled;
    private boolean isBluetoothEnabled;
	
 
    public String getHDD() {
        return HDD;
    }
 
    public String getRAM() {
        return RAM;
    }
 
    public boolean isGraphicsCardEnabled() {
        return isGraphicsCardEnabled;
    }
 
    public boolean isBluetoothEnabled() {
        return isBluetoothEnabled;
    }
	
    private Computer(ComputerBuilder builder) {
        this.HDD=builder.HDD;
        this.RAM=builder.RAM;
        this.isGraphicsCardEnabled=builder.isGraphicsCardEnabled;
        this.isBluetoothEnabled=builder.isBluetoothEnabled;
    }
	
    //Builder Class
    public static class ComputerBuilder{
 
        // required parameters
        private String HDD;
        private String RAM;
 
        // optional parameters
        private boolean isGraphicsCardEnabled;
        private boolean isBluetoothEnabled;
		
        public ComputerBuilder(String hdd, String ram){
            this.HDD=hdd;
            this.RAM=ram;
        }
 
        public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) {
            this.isGraphicsCardEnabled = isGraphicsCardEnabled;
            return this;
        }
 
        public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
            this.isBluetoothEnabled = isBluetoothEnabled;
            return this;
        }
		
        public Computer build(){
            return new Computer(this);
        }
 
    }
 
}

(추가) Lombok의 Builder

클래스 레벨 어노테이션

@Builder
public class BuildMe {

    private String username;
    private int age;

    public BuildMe(String username) {
        this.username = username;
        this.age = 1;
    }
}

생성자 레벨 어노테이션

public class BuildMe {

    private String username;
    private int age;

    @Builder
    public BuildMe(String username) {
        this.username = "Mr/Mrs. " + username;
        this.age = 1;
    }

}

기본적으로 builder 패턴을 사용할 때 default 값들을 지정하기 좋은 방식이다.

반드시 초기화되어야 하는 필드의 경우 lombok의 @Builder.Default 속성을 사용하거나 선언 시점에 또는 생성자에서 초기화하는 것이 좋다.

@Singular("alias")
@Singular 어노테이션을 사용하면 리스트 같은 컬렉션 객체를 빌더 패턴으로 다룰 때 리스트 객체 자체를 넘기는 게 아니라 해당 리스트에 요소를 추가하는 방식으로 생성할 수 있다.

XxxString.builder()
.alias("String")

profile
노력하는 사람

0개의 댓글