S E P H ' S

[생성 패턴] 빌더(Builder) 패턴 본문

Programing & Coding/Design Pattern

[생성 패턴] 빌더(Builder) 패턴

yoseph0310 2023. 3. 5. 18:18

빌더(Builder) 패턴

빌더 패턴은 많은 Optional한 멤버 변수(혹은 파라미터)나 지속성 없는 상태 값들에 대해 처리해야 하는 문제들을 해결한다. 예를 들어, 추상 팩토리 패턴이나 팩토리 패턴에서는 생성해야 하는 클래스에 대한 속성 값이 많을 때 아래와 같은 이슈가 있다.

 

  1. 클라이언트 프로그램으로부터 팩토리 클래스로 많은 파라미터를 넘겨줄 때 타입, 순서 등에 대한 관리가 어려워져 에러 발생 확률이 높아짐
  2. 경우에 따라 필요 없는 파라미터들에 대해 팩토리 클래스에 일일이 null 값을 넘겨 줘야 한다.
  3. 생성해야 하는 서브 클래스가 무거워 지고 복잡해짐에 따라 팩토리 클래스 또한 복잡해진다.

빌더 패턴은 이러한 문제들을 해결하기 위해 별도의 Builder 클래스를 만들어 필수 값에 대해서는 생성자를 통해, 선택적인 값들에 대해서는 메소드를 통해 step-by-step으로 값을 입력 받은 후에 build() 메소드를 통해 최종적으로 하나의 인스턴스를 관리하는 방식이다.

 

빌더 패턴은 굉장히 자주 사용되는 생성 패턴 중 하나로, Retrofit이나 Okhttp 등 유명 오픈소스에서도 이 빌더 패턴을 사용하고 있다.

 

빌더 패턴 구현 방법

  1. 빌더 클래스를 Static Nested Class로 생성한다. 이때, 관례적으로 생성하고자 하는 클래스 이름 뒤에 Builder를 붙인다. 예를 들어, Computer 클래스에 대한 빌더 클래스의 이름은 ComputerBuilder라고 정의한다.
  2. 빌더 클래스의 생성자는 public으로 하며, 필수 값들에 대해 생성자의 파라미터로 받는다.
  3. 옵셔널한 값들에 대해서는 각각의 속성마다 메소드로 제공하며, 이때 중요한 것은 메소드의 리턴 값이 빌더 객체 자신이어야 한다.
  4. 마지막 단계로, 빌더 클래스 내에 build() 메소드를 정의하여 클라이언트 프로그램에게 최종 생성된 결과물을 제공한다. 이렇듯 build() 를 통해서만 객체 생성을 제공하기 때문에 생성 대상이 되는 클래스의 생성자는 private으로 정의해야 한다.
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.isGraphicsEnabled;
        this.isBluetoothEnabled = builder.isBluetoothEnabled;
    }
    
    // 빌더 클래스
    public static class ComputerBuilder {
        
        private String HDD;
        private String RAM;
        
        private boolean isGraphicsEnabled;
        private boolean isBluetoothEnabled;
        
        public ComputerBuilder(String hdd, String ram) {
            this.HDD = hdd;
            this.RAM = ram;
        }

        public ComputerBuilder setGraphicsEnabled(boolean isGraphicsEnabled) {
            this.isGraphicsEnabled = isGraphicsEnabled;
            return this;
            
        }

        public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) {
            this.isBluetoothEnabled = isBluetoothEnabled;
            return this;
        }
        
        public Computer build() {
            return new Computer(this);
        }
    }
}

 

주의 깊게 볼 것은 Computer 클래스가 setter 메소드 없이 getter 메소드만 가진다는 것과 public 생성자가 없다는 것이다. 그렇기 때문에 Computer 객체를 얻기 위해서는 오직 ComputerBuilder 클래스를 통해서만 가능하다.

 

public class TestBuilderPattern {
    public static void main(String[] args) {
        Computer comp = new Computer.ComputerBuilder("500GB", "2GB")
                .setBluetoothEnabled(true)
                .setGraphicsEnabled(true)
                .build();
    }
}

 

Spring에서 @Builder로 Builder 패턴 적용하기

lombok의 @Builder 어노테이션으로 손쉽게 Builder 패턴을 생성해서 사용할 수 있다. Builder 어노테이션은 아래의 코드로 작동하게 된다. (어노테이션만 달면 되는 것이므로 따로 작성할 필요 없음) 빌더 패턴을 적용하려는 클래스나 클래스의 생성자에 어노테이션을 적용하면 된다. 마찬가지로 필수 인자에 대해서는 생성자로, 선택적 인자에 대해서는 메소드를 만들어 관리한다. 이때, 가독성을 위해 단순히 setter로써 사용하기 보다는 목적이 분명히 드러나는 네이밍을 사용하는 것을 권장한다.

 

Before:
   @Builder
   class Example<T> {
   	private T foo;
   	private final String bar;
   }
   
After:
   class Example<T> {
   	private T foo;
   	private final String bar;
   	
   	private Example(T foo, String bar) {
   		this.foo = foo;
   		this.bar = bar;
   	}
   	
   	public static <T> ExampleBuilder<T> builder() {
   		return new ExampleBuilder<T>();
   	}
   	
   	public static class ExampleBuilder<T> {
   		private T foo;
   		private String bar;
   		
   		private ExampleBuilder() {}
   		
   		public ExampleBuilder foo(T foo) {
   			this.foo = foo;
   			return this;
   		}
   		
   		public ExampleBuilder bar(String bar) {
   			this.bar = bar;
   			return this;
   		}
   		
   		@java.lang.Override public String toString() {
   			return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
   		}
   		
   		public Example build() {
   			return new Example(foo, bar);
   		}
   	}
   }

 

 

 

출처 : https://readystory.tistory.com/121