OOP/Design Pattern

[OOP] 빌더 패턴 ( Builder Pattern )

IT록흐 2023. 7. 27. 18:06
반응형

디자인 패턴이란?

객체지향설계 과정에서 발생하는 문제들을 해결하기 위한 패턴(Pattern)

 

문제 상황

 

필드변수가 많은 객체는 상황에 따라 세팅이 필요한 변수도 있고 필요하지 않은 변수도 있다. 그러므로 필드변수가 많은 객체는 여러 상황을 지원할 수 있도록 유연한 데이터 세팅이 필요하다. 예를 들어, 문장 객체를 생각해보자. 문장 하나가 만들어지는 데에는 많은 요소가 필요하다.

 

1. 주어 ( 명사 )

2. 서술어 ( 동사 )

3. 목적어 ( 명사 )

4. 수식어 ( 부사, 형용사 )

등등

 

문장은 다양한 형태가 있기에 각 요소가 필요할 수도, 필요 없을 수도 있다. 그러므로 문장 객체는 최대한 유연하게 데이터를 세팅해야 한다. 그러나 기존의 생성자, 수정자 방식의 데이터 세팅은 유연함과는 거리가 멀다.

 

생성자 방식

public class Sentence {
    SubjectWord subjectWord; // 주어
    VerbWord verbWord; // 서술어
    ObjectWord objectWord; // 목적어 
    AdverbWord adverbWord; // 수식어 ( 부사 )
    AdjectiveWord adjectiveWord; // 수식어 ( 형용사 )

    public Sentence(SubjectWord subjectWord, VerbWord verbWord, ObjectWord objectWord, AdverbWord adverbWord, AdjectiveWord adjectiveWord) {
        this.subjectWord = subjectWord;
        this.verbWord = verbWord;
        this.objectWord = objectWord;
        this.adverbWord = adverbWord;
        this.adjectiveWord = adjectiveWord;
    }
}

 

생성자 방식은 생성자 매개변수로 데이터를 받아 필드변수에 세팅하는 방식이다. 만약 주어와 수식어가 없는 문장이라면 주어 데이터와 수식어 데이터에는 Null값이 들어가야 한다. Null 값을 세팅하기 싫다면 주어, 수식어 매개변수가 빠진 생성자를 하나 더 만들면 된다. 그러나 앞서 말했듯 문장은 정말 다양한 형태로 존재한다. 다양한 형태를 지원하는 생성자를 모두 만든다면 굉장한 낭비이고 유연하지 못하다.

 

그럼 수정자는 어떨까?

 

수정자 방식

public class Sentence {
    SubjectWord subjectWord; // 주어
    VerbWord verbWord; // 서술어
    ObjectWord objectWord; // 목적어 
    AdverbWord adverbWord; // 수식어 ( 부사 )
    AdjectiveWord adjectiveWord; // 수식어 ( 형용사 )
    
    public void setSubjectWord(SubjectWord subjectWord) {
        this.subjectWord = subjectWord;
    }
    public void setVerbWord(VerbWord verbWord) {
        this.verbWord = verbWord;
    }
    public void setObjectWord(ObjectWord objectWord) {
        this.objectWord = objectWord;
    }
    public void setAdverbWord(AdverbWord adverbWord) {
        this.adverbWord = adverbWord;
    }
    public void setAdjectiveWord(AdjectiveWord adjectiveWord) {
        this.adjectiveWord = adjectiveWord;
    }
}

 

 

수정자 방식은 생성자 방식과 달리, setter함수를 호출하여 데이터를 세팅하니 생성자 방식보다 확실히 유연하다. 그러나 수정자 방식은 문제가 있다. 객체지향설계의 가장 중요한 원리 중 하나가 정보은닉이다. setter 함수는 필드변수의 이름과 타입을 외부로 노출시킨다. setter 함수는 클래스의 내부정보를 외부로 노출시켜 정보은닉이 이루어지지 않는다. 

 

public class Sentence {
    SubjectWord subjectWord; // 주어
    VerbWord verbWord; // 서술어
    ObjectWord objectWord; // 목적어
    AdverbWord adverbWord; // 수식어 ( 부사 )
    AdjectiveWord adjectiveWord; // 수식어 ( 형용사 )
    
    public void setBasicSentence(SubjectWord sub, ObjectWord object, VerbWord verb){
        this.subjectWord =sub;
        this.objectWord = object;
        this.verbWord =verb;
    }
    public void setSimpleSentence(VerbWord verb, ObjectWord object){
        this.verbWord = verb;
        this.objectWord = object;
    }
}

 

setter함수 대신에 사용되는 메서드는 위와 같다. 메서드는 클라이언트가 사용한다. 그러므로 메서드의 기능이 이름에 명시되어 있는 것이 좋다. setBasiceSentence 메서드명은 주어 + 목적어 + 서술어 형식의 기본 문장구조를 위한 데이터 설정 메서드임을 나타낸다.  setSimpleSentence는 목적어 + 서술어로 구성된 단순한 형식의 문장구조를 위한 데이터 세팅 메서드임을 나타낸다. 이러한 메서드는 특정 기능에 문제가 발생하면 기능이름이 명시된 메서드의 로직을 살펴볼 수 있어 유지보수에 좋다. setter 함수는 단순한 데이터 설정 메소드이기에 유지보수에 좋지 못하고 정보도 은닉하지 못한다. 

 

또한 setter함수는 객체의 필드변수에 직접 접근하여 데이터를 변경하는 메서드다. 데이터는 예상 가능한 제어 안에서 변경되어야 한다. 개발자가 예상할 수 없는 제어에 의해 데이터가 변경된다면 데이터 정합성을 유지할 수없다. 그러므로 완전히 변경 가능성을 제거하려면 setter함수를 사용하지 않는 것이 좋다

 

이러한 이유로 setter함수를 이용한 데이터 세팅은 지양되어야 한다.  이렇듯, 생성자, 수정자 방식은 다양한 데이터를 유연하게 세팅할 수 있는 환경을 제공하지 못한다. 

 

 

해결책

 

 

 

 

기존에는 클라이언트가 Sentence 객체에 직접 접근했다. 직접 접근하여 생성자나 수정자로 데이터를 세팅하고 원하는 Sentence 객체를 생성했다.

 

 

- 빌더 패턴 ( Builder Pattern )

 

 

 

빌더 패턴은 클라이언트가 SentenceBuilder에게 Sentence 객체를 요청하는 구조이다.  요청하는 형태가 굉장히 독특하다. 메소드 체이닝 방식(method chaining)으로 객체를 요청한다. 

 

클라이언트

public class Client {
    public static void main(String[] args) {

        SentenceBuilder sentenceBuilder = new KoreanBuilder(); //SenteceBuilder 생성
        Sentence sentence = sentenceBuilder.create().buildSubject(new SubjectWord("나는")) // 메소드 체이닝 방식
                .buildObject(new ObjectWord("축구를"))
                .buildVerb(new VerbWord("한다"))
                .getSentence();
        
    }
}

 

위 코드를 보면, 클라이언트가 SentenceBuilder의 메소드를 '.' 연산자로 호출하고 다시 다른 메소드를 '.' 연산자로 호출하는 체인(Chain) 구조로 이루어져 있음을 알 수 있다. 이런 구조가 가능한 이유는 메소드의 반환타입을 하나로 통일했기 때문이다.

 

SentenceBuilder 추상클래스 

public abstract class SentenceBuilder {
    protected  Sentence sentence; // Sentence 객체

    public Sentence getSentence() {
        return sentence;
    }
    public SentenceBuilder create(){ // Sentence 객체 생성
        this.sentence = new Sentence();
        return this;
    }

    // 추상 메소드 모음
    public abstract SentenceBuilder buildSubject(SubjectWord subject);
    public abstract SentenceBuilder buildVerb(VerbWord verb);
    public abstract SentenceBuilder buildObject(ObjectWord object);
    public abstract SentenceBuilder buildAdverb(AdverbWord adverb);
    public abstract SentenceBuilder buildAdjective(AdjectiveWord adjective);

}

 

SentenceBuilder 클래스의 코드를 보면, 메소드의 반환타입이 SentenceBuilder, 자기 자신으로 통일되어 있음을 알 수 있다. 빌더 패턴의 목적은 데이터 세팅이다. 하나의 메소드는 하나의 데이터 세팅의 책임을 갖는다. 하나의 데이터 세팅이 끝나면 다음 데이터 세팅을 위해, 자기자신을 다시 반환하는 것이다. 그러면 메소드가 메소드의 꼬리를 무는 체인 구조를 구현할 수 있다. 

 

KoreanBuilder 클래스

public class KoreanBuilder extends SentenceBuilder {
    @Override
    public SentenceBuilder buildSubject(SubjectWord subject) {
        sentence.setSubjectWord(subject);
        return this;
    }

    @Override
    public SentenceBuilder buildVerb(VerbWord verb) {
        sentence.setVerbWord(verb);
        return this;
    }

    @Override
    public SentenceBuilder buildObject(ObjectWord object) {
        sentence.setObjectWord(object);
        return this;
    }
    @Override
    public SentenceBuilder buildAdverb(AdverbWord adverb) {
        sentence.setAdverbWord(adverb);
        return this;
    }

    @Override
    public SentenceBuilder buildAdjective(AdjectiveWord adjective) {
        sentence.setAdjectiveWord(adjective);
        return this;
    }
}

 

KoreanBuilder 클래스는 SentenceBuilder 추상클래스를 구현하는 클래스이다. KoreanBuilder 클래스가 setter함수를 이용하여 데이터 세팅을 실제로 구현하고 있다. 이와같이, setter함수가 직접 Client에 노출되는 것이 아니라, Builder에 숨겨져 사용된다면 정보은닉을 구현할 수 있다. 

 

이렇듯, 빌더 패턴(Builder Pattern)은 생성자, 수정자 방식의 한계를 보완한다. 세팅이 필요한 데이터의 메소드만 골라서 호출할 수 있어 유연하고 메소드가 체인 구조로 호출되어 가독성도 올라간다. 또한 setter함수도 내부로 숨길 수 있어 정보은닉 효과도 얻을 수 있다. 이처럼 다양한 필드변수가 존재하여 다양한 형태의 객체가 생성되어야 하는 경우, 빌더 패턴을 사용하면 유용하다. 

 

 

 


 

참고자료

 

[Design Pattern] GoF 생성 패턴 - 빌더 패턴(Builder Pattern) - HERSTORY

빌더 패턴(Builder Pattern) GoF 디자인 패턴 중 생성 패턴에 해당한다. 빌더 패턴은 복잡한 객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방

4z7l.github.io

 

[Java] 빌더 패턴(Builder Pattern)을 사용해야 하는 이유

객체를 생성하기 위해서는 생성자 패턴, 정적 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있습니다. 개인적으로 객체를 생성할 때에는 반드시 빌더 패턴을 사용해야 한다고 생각하는

mangkyu.tistory.com

 

[디자인패턴] Builder Pattern / 빌더패턴 구현

빌더(Builder) 패턴이란? GoF 중 생성 패턴에 속하며, 객체를 생성할 때 유용하게 사용하는 디자인패턴이다. 기존에 생성자를 이용하여 객체를 생성하는데 생길 수 있는 문제를 보완하기 위해 사용

foot-develop.tistory.com

 

 

반응형