객체 생성 패턴
업데이트:
Effective Java를 1회차를 독파하였고, 중요하다 생각하는 내용을 정리하여 포스팅하려 합니다.
그 첫 번째는 객체 생성 패턴입니다.
📌 모든 소스는 Github에 있습니다.
제가 경험한 바로 객체(주로 Domain
객체겠죠?)를 생성, 값을 바인딩하는 방법은 총 4가지로 나뉩니다.
- 기본 객체 생성자를 통해 객체 생성 →
Setter
메소드를 통해 객체 값 바인딩 - 필수값, 옵셔널한 값에 따라 여러 개의 생성자를 두어 필요할때 마다 사용
Static Factory Method
사용 → Effective Java 내용Builder
패턴 사용 → Effective Java 내용
기본 생성자, Setter
기본 생성자와 Setter를 이용한 코드는 다음과 같습니다.
Post post = new Post();
post.setTitle("title");
post.setAuthor("author");
post.setContent("content");
아직도 많은 회사들이 위의 코드를 사용하는 것을 볼 수 있는데, 이 패턴은 안좋은 이유가 몇 가지 있습니다.
- 기본 생성자 접근을 오픈함으로써 무분별한 객체 생성을 막을 수 없습니다.
- 객체가 가져야할 필수 값을 누락할 소지가 있습니다.
❗그리고
Setter
의 안좋은 점을 그대로 가져오는데,Setter
의 안좋은 점은 다른 포스팅에서 다루도록 하겠습니다.
여러 개의 생성자
public Post(String title) {
new Post(null, title, null, null);
}
public Post(String title, String content) {
new Post(null, title, content, null);
}
public Post(String title, String content, String author) {
new Post(null, title, content, author);
}
public Post(Long id, String title, String content, String author) {
this.id = id;
this.title = title;
this.content = content;
this.author = author;
}
이 패턴은 다음과 같은 단점을 가지고 있습니다.
- 유지보수 포인트가 늘어납니다.
- 데이터 타입이 동일 시 되는 변수를
parameter
로 넣을 때, 혼동이 올 수 있습니다. (요새는 IDE가 잘 보여주긴 합니다만)
Static Factory Method
public static Post ofTitle(String title) {
Post post = new Post();
post.title = title;
return post;
}
public static Post ofTitleAndContent(String title, String content) {
Post post = new Post();
post.title = title;
post.content = content;
return post;
}
장점
- 메서드가 이름을 가질 수 있습니다.
- Simple하고 명확하게 사용 가능합니다.
- 다양하게 사용 가능합니다.
- 객체의 타입 변환하는 방향으로 메소드를 짤 수 있습니다.
Dto
→Entity
- 객체의 타입 변환하는 방향으로 메소드를 짤 수 있습니다.
단점
- 생성자가 없을 수 있어 상속받은
Class
는 만들 수 없습니다. - 프로그래머에게 인지가 잘 되지 않을 수 있습니다.
네이밍 관용어구
from
: 하나의 매개 변수를 받아서 객체를 생성of
: 여러개의 매개 변수를 받아서 객체를 생성getInstance
instance
: 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.newInstance
create
: 새로운 인스턴스를 생성get[OtherType]
: 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.new[OtherType]
: 다른 타입의 새로운 인스턴스를 생성.
출처 : https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
Builder 패턴
객체 생성을 담당하는 Builder
를 만들고 그 Builder
에 값을 바인딩하여 객체를 반환하는 패턴입니다.
public class Car {
private String name;
private String number;
private Integer size;
public Car(CarBuilder carBuilder) {
this.name = carBuilder.name;
this.number = carBuilder.number;
this.size = carBuilder.size;
}
public static CarBuilder builder(String name, String number) {
return new CarBuilder(name, name);
}
public static class CarBuilder {
private final String name;
private final String number;
private Integer size = 0;
public CarBuilder(String name, String number) {
this.name = name;
this.number = number;
}
public CarBuilder size(Integer val) {
this.size = val;
return this;
}
public Car build() {
return new Car(this);
}
}
}
강의를 통해 작성한 코드입니다.
CarBuilder builder = Car.builder("name", "number");
builder.size(0);
Car build = builder.build();
사용하는 방법은 먼저 CarBuilder
를 builder()
메소드를 통해 생성합니다. 이때 필수값인 name
, number
를 인자로 받고, 이 CarBuilder
를 통해 나머지 값인 size
를 바인딩 합니다.
장점
- 상속받은
Class
의Builder
가 정의한builder()
메서드가 아닌 상위 클래스 타입을 반환하는 것이 아닌 자신의 타입을 반환 합니다.
단점
Builder
를 반드시 생성해야 하기 때문에 추가 생성비용이 무조건 필요합니다.- 인자의 갯수가 적다면 일반 생성자를 사용하는 것이 코드의 라인 수를 더 줄이고, 사용하기 편한 방법이라 생각합니다.
💡
Builder
코드를 직접 짠다면 유지보수할 포인트가 또 늘어나게 되겠지만Lombok
의@Builder
어노테이션을 사용하여 최대한 간단하게 사용하는게 좋을 것 같습니다.
Lombok
의 @Builder
어노테이션 활용법에 대해선 다른 포스팅에서 다뤄보도록 하겠습니다.
마무리
객체를 생성하는 여러가지 방법에 대해서 다뤄봤습니다.
개인적으로 Setter
를 지양하고 static factory method
와 Builder
패턴이 자주 사용되어야 한다 생각합니다. (일반 생성자는 상황에 따라~)
고려해야 할 상황이 하나 더 있는데, 바로 비즈니스 로직의 위치입니다.
비즈니스 로직의 위치가 Domain
이 아닌 Service Layer
에 있다면 Setter
를 활용 했을 확률이 높습니다.
즉, DDD
가 아닐 경우입니다. 이럴때 static factory method
와 Builder
를 사용한다 한들 불변객체를 만들지 못하기 때문에 그 효과는 절반으로 줄게 될 것입니다. 결국 DDD
로 바꿔야 한다는 것인데, 아직 Setter
사용에 익숙한 회사들에서 기존 코드를 모두 리팩토링 하면서 static factory method
와 Builder
로 교체하면서 기술 부채를 덜어내는 수고를 들일지가 미지수네요.
객체 생성 패턴에 대한 얘기는 여기서 마무리하고 다음은 Setter
를 지양해야 하는 이유와 @Builder
에 대해서 다뤄보도록 하겠습니다.
여기까지 제 포스트를 읽어주셔서 감사합니다.
항상 건강 조심하시고 행운이 가득하시길 바랍니다~ 👍
댓글남기기