본문 바로가기
java

함수형 인터페이스를 이용한 Builder

by 쭈꾸마뇽 2021. 5. 29.

Builder 패턴

Java에서 객체를 생성하기 위해선 기본적으로 생성자를 이용한다.  필요에 따라서 파라미터가 없는 생성자, 전체 멤버변수를 입력받는 생성자 또는 일부만 받는 생성자를 사용한다.  하지만 이 생성자를 이용해 객체 생성을 할때 불편함이 좀 있다.  만약 클래스의 멤버변수가 엄청 많을 때,  그리고 상황에 따라서 그 중 일부의 멤버변수만 사용하고 그 사용처가 매우 많다면 각 상황별 생성자를 매번 만들어 주는것은 너무 번거롭다.  이를 해소할 수 있는 방법이 Builder이다.

public class Member {
    private String name;
    private int age;
    private String address;

    public Member() {
    }

    public Member(String name) {
        this.name = name;
    }

    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Member(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
public class App {
    public static void main(String[] args) {
        Member member1 = new Member();
        member1.setName("Kang");
        member1.setAge(30);
        member1.setAddress("Seoul");
        
        Member member2 = new Member("Kang");
        
        Member member3 = new Member("Kang", 30);
        
        Member member4 = new Member("Kang", 30, "Seoul");
    }
}

Member 클래스는 3개의 멤버변수를 가지고 있고 각 상황에 따른 생성자를 모두 갖고있다.  이 클래스의 멤버변수가 적어서 다행이지 훨씬 많았다면 생성자를 만들어 주는것도 어려울것이다.  그리고 같은 타입을 갖는 멤버변수가 있다면 해당 멤버변수만을 갖는 생성자를 생성자를 각각 만들어 줄 수 없다.

package functional;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

public class Member {
	...

    static class MemberBuilder {
        private String name;
        private int age;
        private String address;

        public MemberBuilder name(String name) {
            this.name = name;
            return this;
        }

        public MemberBuilder age(int age) {
            this.age = age;
            return this;
        }

        public MemberBuilder address(String address) {
            this.address = address;
            return this;
        }

        public Member build() {
            Member member = new Member();
            member.setName(name);
            member.setAge(age);
            member.setAddress(address);
            return member;
        }
    }
}

Builder 패턴을 사용하기 위해 Member 클래스 안에 static 클래스로 MemberBuilder를 만들어주었다.  MemberBuilder는 Target 클래스인 Member 클래스가 갖는 멤버변수 중 빌더를 통해 초기화할 멤버변수를 선언해 주고 멤버변수를 입력받아 MemberBuilder 본인을 리턴해주는 Setter 역할을 하는 함수를 만들어 준다.  마지막으로 build()를 하면 Member 객체를 생성해 리턴해 준다.  

Member member5 = new Member.MemberBuilder()
                .name("Kang")
                .age(30)
                .address("Seoul")
                .build();

Builder 패턴을 이용하면 상황별 생성자를 모두 만들거나 Setter를 이용해 멤버변수를 초기화하는 방법보다 더 깔끔하게 객체를 생성할 수 있다.

함수형 인터페이스 활용

Builder 패턴을 이용하기 위해서 new 연산자를 사용해서 객체생성을 하는데 함수형 인터페이스를 사용해 new 연산자 없이 Builder 패턴을 사용해보려 한다.

package functional;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

public class Member {
	...

    static public Member create(UnaryOperator<MemberBuilder> composer) {
        return composer.apply(new MemberBuilder()).build();
    }

    static public Member create2(Consumer<Member> composer) {
        Member member = new Member();
        composer.accept(member);
        return member;
    }
    
    ...
}

 

Member member6 = Member.create(memberBuilder -> memberBuilder
    .name("Kang")
    .age(30)
    .address("Seoul")
);

Member member7 = Member.create2(m -> {
    m.setName("Kang");
    m.setAge(30);
    m.setAddress("Seoul");
});

이번에는 Member 객체 안에 함수형 인터페이스를 매개변수로 받는 static 메소드를 두가지 만들었다.  각각 UnaryOperator Consumer를 받으며 UnaryOperator는 Builder를 사용하고 Consumer는 Setter를 이용할 것이다.

 

UnaryOperator를 사용하면 객체를 생성할 때 Builder를 매개변수로 사용하면 되고 Consumer를 사용하면 Setter를 이용해 멤버변수 초기화를 해주면 된다. 

 

얼핏 보면 그냥 Builder 패턴을 사용하는 것과 크게 차이는 없어 보이지만 객체를 생성함에 좀 더 직관적이다.  순수하게 Builder 패턴을 사용할 경우 Builder 클래스로부터 Target 객체가 생성되는 반면 함수형 인터페이스를 사용하면 Target 객체의 create 함수를 이용하기 때문에 좀 더 직관적인 코드 해석이 가능하다.

댓글