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 함수를 이용하기 때문에 좀 더 직관적인 코드 해석이 가능하다.
'java' 카테고리의 다른 글
Design Patterns - Factory Method (0) | 2021.11.17 |
---|---|
Design Patterns - Singleton (0) | 2021.11.11 |
더 자바(java 8) - CompletableFuture (0) | 2021.05.26 |
더 자바(java 8) - Stream, Optional, Date (0) | 2021.05.19 |
더 자바(java 8) - 인터페이스의 변화 (0) | 2021.05.11 |
댓글