디자인 패턴이란?
디자인 패턴은 기존 환경 내에서 반복적으로 일어나는 문제들을 어떻게 풀어나갈 것인가에 대한 일종의 해결책이다. 디자인 패턴하면 떠오르는 'Gof의 디자인 패턴'에서는 객체지향적 디자인 패턴의 카테고리를 "생성패턴(Creational Pattern)", "구조 패턴(Structural Pattern)", "행동 패턴(Behavioral Patter)" 이렇게 3가지로 구분하고 있다.
그중 생성 패턴에 해당하는 Builder 패턴에 대해 알아보자
빌더 패턴(Builder)
빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인패턴이다. 이 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있다.
구조
- Buolder(추상클래스)를 정의하고 이를 상속받은 ConcreteBuilder(서브클래스)를 구현한다.
- Product의 일부가 build 될 때마다 Director는 Builder에 통보한다.
- Builder는 Director의 요청을 처리해 Product에 부품을 추가한다.
이해하기 쉬운 사례로 햄버거를 들 수 있다. 수제 햄버거를 주문할 때 빵이나 패티 등 속재료 들은 주문하는 사람이 마음대로 결정한다. 어느 사람은 치즈를 빼달라고 할 수도 있고, 어떤 사람은 토마토를 빼달라고 할 수 있다.
이처럼 선택적 속재료들을 보다 유연하게 받아 다양한 타입의 인스턴스를 생성할 수 있어, 클래스의 선택적 매개변수가 많은 상황에서 유용하게 사용된다.
다른 예시
문제점
빌더 패턴을 사용하지 않을 때다.
게임에서 캐릭터를 생성할 때 매우 많은 옵션이 있고, 매번 생성할 때마다 이 옵션들을 전부 인자로 넘겨야 한다고 가정해 보자.
// 게임 캐릭터 클래스
public class GameCharacter {
private String name;
private String characterClass;
private int level;
private String weapon;
// 많은 다른 속성들...
// 생성자 - 매우 많은 인자를 받음
public GameCharacter(String name, String characterClass, int level, String weapon /* 많은 다른 속성들... */) {
this.name = name;
this.characterClass = characterClass;
this.level = level;
this.weapon = weapon;
// 다른 속성들 설정...
}
}
이런 방식으로 생성자에 매우 많은 인자를 추가하게 되면, 새로운 캐릭터를 생성할 때 인자의 순서를 정확히 기억하고 전달해야 한다. 또한 특정 속성을 설정하지 않을 경우에도 더미값을 전달해야 하는 불편함이 발생할 수 있다.
빌더 패턴을 사용해 보자.
// 게임 캐릭터 클래스 - 빌더 패턴 적용
public class GameCharacter {
private String name;
private String characterClass;
private int level;
private String weapon;
// 다른 속성들...
private GameCharacter(String name, String characterClass, int level, String weapon /* 많은 다른 속성들... */) {
this.name = name;
this.characterClass = characterClass;
this.level = level;
this.weapon = weapon;
// 다른 속성들 설정...
}
public static class CharacterBuilder {
private String name;
private String characterClass;
private int level;
private String weapon;
// 다른 속성들...
public CharacterBuilder(String name, String characterClass) {
this.name = name;
this.characterClass = characterClass;
}
public CharacterBuilder setLevel(int level) {
this.level = level;
return this;
}
public CharacterBuilder setWeapon(String weapon) {
this.weapon = weapon;
return this;
}
// 다른 속성 설정 메서드들...
public GameCharacter build() {
return new GameCharacter(name, characterClass, level, weapon /* 많은 다른 속성들... */);
}
}
}
이제 빌더 패턴을 사용하면 캐릭터를 생성할 때 필요한 속성들을 필요한 만큼 설정할 수 있다. 인자의 순서에 구애받지 않고, 필요한 속성만 설정하여 객체를 생성할 수 있다. 코드의 가독성과 유지보수성이 향상된다.
public class Game {
public static void main(String[] args) {
GameCharacter warrior = new GameCharacter.CharacterBuilder("Aragorn", "Warrior")
.setLevel(30)
.setWeapon("Sword")
.build();
GameCharacter mage = new GameCharacter.CharacterBuilder("Gandalf", "Mage")
.setLevel(50)
.setWeapon("Staff")
.build();
}
}
장단점
장점
- 가독성 향상: 객체 생성을 위한 복잡한 로직이나 많은 파라미터를 가진 생성자를 사용하지 않고, 체이닝 방식으로 객체를 구성할 수 있어 가독성이 높아진다.
- 유연성: 필요한 속성만 설정하여 객체를 생성할 수 있다. 특정 속성이나 옵션을 설정하지 않아도 기본값을 사용하거나 생략할 수 있다.
- 객체 생성 과정의 단순화: 복잡한 객체의 생성 과정을 캡슐화하여 사용자가 직접적으로 그 과정을 알 필요 없이 객체를 생성할 수 있다.
단점
- 코드 추가: 빌더 클래스를 작성하는 과정에서 추가적인 코드 작성이 필요하다. 특히, 많은 속성이 있는 객체의 경우에는 빌더 클래스의 작성이 복잡해질 수 있다.
- 메모리 사용량 증가: 빌더 패턴은 객체를 생성하기 위한 별도의 빌더 객체를 생성하기 때문에 일부 메모리를 더 사용할 수 있다. 하지만 이는 대부분의 경우 무시할 수 있는 수준이다.
참고:
'CS' 카테고리의 다른 글
디자인패턴 - 프로토타입 패턴(Prototype) (0) | 2023.11.19 |
---|---|
디자인 패턴 - 전략 패턴(Strategy) (0) | 2023.11.12 |
GoF 디자인 패턴: 소프트웨어 디자인의 핵심 (0) | 2023.11.05 |
CSR 과 SSR 그리고 SPA (1) | 2023.10.28 |
jOOQ 란 (0) | 2023.04.28 |