Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- React
- 가용영역
- 글또10기
- builder-pattern
- 코엑스그랜드볼룸
- object-creation
- 프로그래머스
- QueryDSL
- 글또
- 코딩테스트
- SpringBoot
- 헥사고날 아키텍처
- constructor
- ReverseNested
- UserLand
- 레벨1
- 클린 아키텍처
- 글쓰기세미나
- 다짐글
- static-factory-method
- 회고
- design-pattern
- Level2
- axios
- HashMap
- 포트앤어댑터 아키텍처
- 3계층 아키텍처
- 클라우드아키텍처
- DevOps
- OpenSearch
Archives
- Today
- Total
oguri's garage
Java 객체 생성 패턴 비교 및 선택 가이드 본문
1. 세 가지 주요 객체 생성 패턴
1.1 생성자 패턴
개념
- Java의 기본적인 객체 생성 방식
- 오버로딩을 통해 여러 생성자 제공
- 컴파일 시점에 타입 안전성 보장
예시
public class User {
private String name;
private String email;
private Integer age;
// 기본 생성자
public User() {}
// 필수 필드만 포함
public User(String name, String email) {
this.name = name;
this.email = email;
}
// 모든 필드 포함
public User(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
}
// 사용법
User user1 = new User("John", "john@example.com");
User user2 = new User("Jane", "jane@example.com", 25);
장점
- 간단하고 직관적
- 컴파일 시점 타입 안전성 보장
- IDE 지원 완벽 (자동완성, 리팩토링 등)
- 성능 최적화
단점
- 매개변수가 많아지면 가독성 저하
- 선택적 매개변수 처리 시 생성자 오버로딩 과도해짐
- 매개변수 순서 혼동 위험
- 같은 타입 매개변수가 많으면 실수 발생
1.2 빌더 패턴
개념
- 객체 생성 과정과 표현 방법을 분리
- 단계별로 객체 생성하며 선택적 매개변수 설정
- 최종적으로
build()메소드로 불변 객체 생성
예시
@Builder
public class User {
private String name;
private String email;
private Integer age;
private String address;
private String phone;
}
// 사용법
User user = User.builder()
.name("John")
.email("john@example.com")
.age(25)
.address("Seoul")
.build();
장점
- 매개변수가 많거나 선택적인 경우 유연하게 대응
- 가독성이 좋고 사용하기 쉬움
- 불변 객체를 쉽게 만들 수 있음
- 매개변수 순서에 상관없이 설정
- 필드명이 명시되어 의미 명확
단점
- 별도의 빌더 클래스 필요로 코드량 증가
- 간단한 객체 생성에는 과도한 설정
- 약간의 성능 오버헤드
- 빌더 객체 생성 비용
1.3 정적 팩토리 메소드
개념
- 객체 생성을 캡슐화하는 static 메소드 제공
- 메소드 이름을 통해 객체 생성 의도를 명확히 표현
예시
public class User {
private String name;
private String email;
private Integer age;
private User(String name, String email, Integer age) {
this.name = name;
this.email = email;
this.age = age;
}
// 정적 팩토리 메소드들
public static User createGuest() {
return new User("Guest", "guest@system.com", null);
}
public static User createUser(String name, String email) {
return new User(name, email, null);
}
public static User createAdult(String name, String email) {
return new User(name, email, 18);
}
// 캐싱된 인스턴스 반환
private static final User ADMIN_USER = new User("Admin", "admin@system.com", null);
public static User admin() {
return ADMIN_USER;
}
// 조건부 객체 생성
public static User fromAge(String name, String email, int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
return new User(name, email, age);
}
}
// 사용법
User guest = User.createGuest();
User adult = User.createAdult("John", "john@example.com");
User admin = User.admin(); // 캐싱된 인스턴스
장점
- 메소드 이름으로 생성 의도를 명확히 전달
- 호출할 때마다 새 객체를 생성할 필요 없음 (캐싱 가능)
- 반환 타입의 하위 타입 객체 반환 가능 (유연성)
- 입력 매개변수에 따라 다른 클래스 객체 반환 가능
- 인스턴스 생성 비용이 높은 객체에서 성능 이점
단점
- 상속을 통한 확장이 어려울 수 있음 (생성자가 private인 경우)
- 다른 static 메소드와 구분이 어려울 수 있음
- API 문서화가 중요함
2. 현대적 패턴: of() 메소드 + 람다
2.1 전통적 빌더 vs of() 메소드
전통적 빌더
HttpClient client = HttpClient.builder()
.url("https://api.example.com")
.method("POST")
.timeout(Duration.ofSeconds(30))
.header("Content-Type", "application/json")
.build();
of() 메소드 + 람다
HttpClient client = HttpClient.of(builder -> {
builder.url("https://api.example.com");
builder.method("POST");
builder.timeout(Duration.ofSeconds(30));
builder.header("Content-Type", "application/json");
});
2.2 of() 메소드의 장점
1. 메모리 효율성
- 기존 빌더: 각 체이닝 단계에서 중간 빌더 객체 생성
- of() 방식: 빌더 객체를 한 번만 생성하고 람다 내에서 즉시 설정
2. JIT 컴파일러 최적화
- 람다 표현식은 JIT 컴파일러의 인라인 최적화에 유리
- 메소드 호출 오버헤드 감소
3. 안전한 객체 생성
build()호출 누락 위험 없음- 람다 내부에서 모든 필드 설정 후 바로 객체 반환
4. 코드 간결성
- 중복된
build()호출 제거 - 명확한 컨텍스트 유지
2.3 구현 예시
public class HttpClient {
private String url;
private String method;
private Duration timeout;
private Map<String, String> headers;
private HttpClient(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.timeout = builder.timeout;
this.headers = builder.headers;
}
// 현대적 of() 메소드
public static HttpClient of(Consumer<Builder> builderConsumer) {
Builder builder = new Builder();
builderConsumer.accept(builder);
return new HttpClient(builder);
}
// 전통적 builder() 메소드도 함께 제공
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String url;
private String method = "GET";
private Duration timeout = Duration.ofSeconds(30);
private Map<String, String> headers = new HashMap<>();
public Builder url(String url) { this.url = url; return this; }
public Builder method(String method) { this.method = method; return this; }
public Builder timeout(Duration timeout) { this.timeout = timeout; return this; }
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public HttpClient build() {
return new HttpClient(this);
}
}
}
3. 패턴 선택 기준
3.1 객체 복잡성 기준
| 필드 수 | 권장 패턴 | 이유 |
|---|---|---|
| 1-2개 | 생성자 | 간단하고 직관적 |
| 3-5개 | 생성자 또는 빌더 | 선택적 매개변수 여부에 따라 |
| 6개 이상 | 빌더 패턴 | 가독성과 유지보수성 |
3.2 상황별 선택 가이드
// 🎯 간단한 값 객체 → 생성자
public class Point {
private final int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
// 🎯 선택적 매개변수가 많음 → 빌더 패턴
@Builder
public class HttpRequest {
private String url; // 필수
private String method; // 선택 (기본: GET)
private Map<String, String> headers; // 선택
private String body; // 선택
private Duration timeout; // 선택
private boolean followRedirects; // 선택
}
// 🎯 생성 의도를 명확히 해야 함 → 정적 팩토리
public class LocalDateTime {
public static LocalDateTime now() { ... }
public static LocalDateTime of(int year, int month, int day) { ... }
public static LocalDateTime parse(String text) { ... }
}
// 🎯 인스턴스 재사용 필요 → 정적 팩토리
public enum Direction {
NORTH, SOUTH, EAST, WEST;
public static Direction fromAngle(double angle) {
// 각도에 따라 적절한 enum 반환
}
}
3.3 성능 고려사항
| 패턴 | 메모리 사용량 | 실행 속도 | 컴파일 시간 |
|---|---|---|---|
| 생성자 | 최적 | 최고속 | 최단 |
| 정적 팩토리 | 캐싱 시 최적 | 캐싱 시 최고속 | 짧음 |
| 빌더 | 추가 객체 생성 | 약간 느림 | 보통 |
| of() + 람다 | 최적화됨 | JIT 최적화 혜택 | 보통 |
4. 실무 권장 패턴
4.1 레이어별 권장사항
Entity Layer
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id private Long id;
private String name;
// 정적 팩토리 메소드 조합
public static User createUser(String name) {
return User.builder().name(name).build();
}
@Builder
private User(String name) {
this.name = name;
}
}
DTO Layer
@Builder
public class UserResponse {
private Long id;
private String name;
private String email;
}
Configuration Layer
// 복잡한 설정이 필요한 경우 of() 메소드 활용
DataSource dataSource = DataSourceConfig.of(config -> {
config.url("jdbc:mysql://localhost/db");
config.username("user");
config.password("password");
config.maxPoolSize(20);
config.connectionTimeout(Duration.ofSeconds(30));
});
4.2 팀 컨벤션 예시
/**
* 객체 생성 패턴 선택 기준:
*
* 1. 필드 3개 이하 + 필수 매개변수만 → 생성자
* 2. 필드 4개 이상 또는 선택적 매개변수 → 빌더 패턴
* 3. 생성 의도 명확화 필요 → 정적 팩토리 메소드
* 4. 인스턴스 캐싱/재사용 → 정적 팩토리 메소드
* 5. 복잡한 설정 API → of() + 람다 패턴
*/
// ✅ 간단한 값 객체
public class Coordinate {
public Coordinate(double x, double y) { ... }
}
// ✅ 선택적 매개변수가 있는 복잡한 객체
@Builder
public class EmailMessage {
private String to; // 필수
private String subject; // 필수
private String body; // 선택
private List<String> cc; // 선택
private Priority priority; // 선택
}
// ✅ 특별한 생성 로직
public class User {
public static User guest() { ... }
public static User admin() { ... }
public static User fromEmail(String email) { ... }
}
5. 결론
5.1 기본 원칙
- 단순함을 우선하라: 복잡하지 않다면 생성자 사용
- 가독성을 고려하라: 매개변수가 많으면 빌더 패턴
- 의도를 명확히 하라: 특별한 생성 로직은 정적 팩토리
- 성능을 고려하라: 자주 사용되는 객체는 캐싱 검토
- 일관성을 유지하라: 팀 내에서 일관된 패턴 사용
5.2 최종 선택 가이드
객체가 간단한가?
├─ YES → 생성자 패턴
└─ NO → 선택적 매개변수가 많은가?
├─ YES → 빌더 패턴
└─ NO → 특별한 생성 로직이 필요한가?
├─ YES → 정적 팩토리 메소드
└─ NO → 생성자 또는 빌더 선택
각 패턴은 고유한 장단점이 있으므로, 상황에 맞는 적절한 선택이 중요합니다.
'개발하다 > Java' 카테고리의 다른 글
| Java 문자열 조합, String.format()이 가독성에 좋은 이유 (0) | 2025.10.19 |
|---|---|
| (Java) Stream API에서 Checked Exception 처리하기 (0) | 2025.10.07 |
| (Java) Record - Lombok Builder vs Record 비교 (0) | 2025.10.05 |
| `null` 대신 `Optional.empty`를 return 해야 하는 이유 (0) | 2025.09.30 |
| HashMap 사용 방법 (5) | 2024.05.28 |