티스토리 뷰

Java

Builder Pattern

LichKing 2017. 12. 31. 20:08

EPL 팀의 리그순위 정보를 담고있는 객체를 만든다고 생각해보자. 보통 순위페이지에 나와있는 정보는 경기 수, 승점, 승, 무, 패, 득실차가 있다. 이 데이터들을 담는 클래스를 정의하자.


class Team{
private String name;
private int playCount;
private int victoryPoint;
private int winCount;
private int drawCount;
private int loseCount;
private int scorePoint;
}


이 객체를 생성하고, 필드들을 주입해줘야한다. 간단하게 setter를 만들어서 주입할 수 있다.


Team team = new Team();
team.setName("맨유");
team.setPlayCount(3);
team.setVictoryPoint(6);
team.setWinCount(2);
team.setDrawCount(0);
team.setLoseCount(1);
team.setScorePoint(4);


빈 객체 생성 후 setter를 이용한 주입은 실무에서도 어렵지않게 볼 수 있는 코드이다. 하지만 setter 주입은 하는 일이 매우 단순하고, 객체 생성 시점에 필요한 모든 값들을 주입하지않아 개발자의 실수가 발생할 수 있으며, public 으로 공개해놓은 set 메서드는 코드 다른부분에서 언제 호출되어 값이 바뀔지 알기힘들다. 큰 고민없이 자바빈 규약에 따른 getter, setter는 객체지향적인 코드작성에 가장 큰 적이라고 할 수 있다.


객체를 생성하는 시점에 모든 필드를 주입받아 객체 사용시점에 비어있는 필드가 존재하지못하도록 생성자 주입을 받아보자.


Team team = new Team("맨유", 3, 6, 2, 0, 1, 4);


기본 생성자가 존재하지않으므로 개발자는 비어있는 객체를 만들 수 없다. 꼭 필드를 넣어줘야하니 비어있는 객체가 있을 확률은 없다. 그런데 저 숫자만 나열되어있는 부분이 뭔가 꺼림찍하다. 필드 순서를 외우지못하면 객체를 만들때마다 어떤 필드에 어떤 값을 넣어야하는지 생성자를 확인해봐야할 것 같다.


이펙티브자바에서는 정적 팩토리 메서드를 권장한다. 만들어보자.


Team team = Team.newInstance("맨유", 3, 6, 2, 0, 1, 4);


음...메서드명을 변경하여 가독성을 끌어올릴땐 정적 팩토리 메서드가 좋겠지만 사실 지금같은 상황에서는 생성자랑 별 차이가 없다. 생성자를 호출할때마다 인자를 확인해서 넣을바엔 차라리 setter로 만드는게 헷갈릴위험이 없을것같기도하다. 이럴때 사용할 수 있는게 Builder 패턴이다. 확인해보자.


class Team {
private String name;
private int playCount;
private int victoryPoint;
private int winCount;
private int drawCount;
private int loseCount;
private int scorePoint;

public static Builder builder() {
return new Builder();
}

public static class Builder {
private String name;
private int playCount;
private int victoryPoint;
private int winCount;
private int drawCount;
private int loseCount;
private int scorePoint;

private Builder() {
}

public Builder name(String name) {
this.name = name;

return this;
}

public Builder playCount(int playCount) {
this.playCount = playCount;

return this;
}

public Builder victoryPoint(int victoryPoint) {
this.victoryPoint = victoryPoint;

return this;
}

public Builder winCount(int winCount) {
this.winCount = winCount;

return this;
}

public Builder drawCount(int drawCount) {
this.drawCount = drawCount;

return this;
}

public Builder loseCount(int loseCount) {
this.loseCount = loseCount;

return this;
}

public Builder scorePoint(int scorePoint) {
this.scorePoint = scorePoint;

return this;
}

public Team build() {
Team team = new Team();
team.name = this.name;
team.playCount = this.playCount;
team.victoryPoint = this.victoryPoint;
team.winCount = this.winCount;
team.drawCount = this.drawCount;
team.loseCount = this.loseCount;
team.scorePoint = this.scorePoint;

return team;
}
}
}


Team 클래스에 Builder 패턴을 이용한 코드를 작성했다. 코드가 길진하지만 비슷한 패턴의 반복이기때문에 어려울건없다. Builder 패턴의 핵심은 Team 객체를 만드는 보조 내부 클래스를 하나 만들고, 내부 클래스의 객체를 이용해 Team 객체를 생성하는 것이다. 사용하는 코드도 확인해보자.


Team team = Team.builder()
.name("맨유")
.playCount(3)
.victoryPoint(6)
.winCount(2)
.drawCount(0)
.loseCount(1)
.scorePoint(4)
.build();


Builder 객체를 이용해 명시적으로 인자를 던져주고 마지막에 build() 메서드를 통해 객체를 생성한다. 바깥에서는 Builder 타입을 참조할 일이 없으므로 build() 를 호출하지않으면 컴파일 에러를 발생시킨다. 처음 setter 주입을 이용할때처럼 빈객체를 생성할 일이 없고(엄밀히 말하면 빈객체를 생성할 여지는 있다. 이럴땐 build() 메서드에 유효성 체크 코드를 넣어 런타임 예외를 발생시키도록 하자.), 생성자나 정적 팩토리 메서드를 이용할때처럼 인자 순서를 외우거나 확인해야할 필요도 없다. 다만 작성해야할 코드가 매우 길어지는 단점이 있긴한데 이럴땐 lombok 라이브러리를 활용하면 라이브러리 차원에서 해결해주니 편리하게 이용할 수 있다.


개인적으로 지금 예제처럼 주입해야할 필드가 여러개고, 그 필드의 타입이 동일해 순서가 바껴도 컴파일 타임에 체크할 수 없는 경우 이 패턴을 활용한다. 생성자, 정적 팩토리 메서드, Builder 를 각 상황에 따라 적절히 활용한다면 안전하고 편리한 객체 생성을 할 수 있을거라고 생각한다.

'Java' 카테고리의 다른 글

Jackson 2.9 에 추가된 @JsonAlias 애노테이션  (0) 2018.01.13
역어셈블리  (0) 2018.01.07
Jackson Serialize 시 클래스 정보가 필요할때  (0) 2017.12.28
Objects 클래스  (3) 2017.12.21
이름 재사용 관련된 용어들  (0) 2017.11.08
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함