| 정적 팩토리 메서드
는 객체를 만드는 특별한 방법임
일반적인 생성자 대신, 클래스 안에 객체를 만드는 전용 메서드를 만들어 사용함.
이 메서드는 이름을 가질 수 있어서 객체가 어떤 목적으로 만들어지는지 더 명확하게 알 수 있음
레고 세트를 생각해보세요. 보통은 설명서를 따라 직접 조립하지만(일반 생성자),
때로는 이미 조립된 부분(예: 우주선 조종석)을 구매할 수 있습니다.
이렇게 미리 조립된 부분을 제공하는 것이 정적 팩토리 메서드와 비슷합니다.
목적에 맞게 미리 구성된 객체를 제공받는 것이죠.
| 정적 팩토리 메서드는 객체 생성을 캡슐화하는 디자인 패턴임
- 이름을 가질 수 있어 생성 목적을 명확하게 표현할 수 있음
- 호출될 때마다 새 객체를 생성할 필요가 없음 (캐싱, 객체 풀링 등 가능)
- 반환 타입의 하위 타입 객체를 반환할 수 있어 유연성이 높음
- 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 없음
- 정적 메서드이므로 상속할 수 없다는 단점
| 주로 사용되는 네이밍 컨벤션
- from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환
- valueOf: from과 of의 더 자세한 버전
- instance 혹은 getInstance : 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않음
- create 혹은 newInstance : 새로운 인스턴스를 생성해 반환함을 보장
| Example
public class User {
private final String name;
private final String email;
private final String type;
private User(String name, String email, String type) {
this.name = name;
this.email = email;
this.type = type;
}
// 일반 사용자 생성
public static User createRegularUser(String name, String email) {
return new User(name, email, "REGULAR");
}
// 관리자 생성
public static User createAdminUser(String name, String email) {
return new User(name, email, "ADMIN");
}
// getter 메서드들...
}
@RestController
public class UserController {
@PostMapping("/user/regular")
public User createRegularUser(@RequestBody UserDTO userDTO) {
return User.createRegularUser(userDTO.getName(), userDTO.getEmail());
}
@PostMapping("/user/admin")
public User createAdminUser(@RequestBody UserDTO userDTO) {
return User.createAdminUser(userDTO.getName(), userDTO.getEmail());
}
}
User 클래스는 두 개의 정적 팩토리 메서드 (createRegularUser와 createAdminUser)를 제공함
사용자 유형에 따라 적절한 User 객체를 생성할 수 있음.
| 정적 팩토리 메서드는 일종의 "생성자를 대신하는 메서드"라고 볼 수 있음
1. 생성자 대체
- 일반적인 생성자 대신 사용됨
- 객체 생성 로직을 캡슐화함
2. 정적 메서드
- 클래스 레벨에서 호출할 수 있는 메서드
- 인스턴스 생성 없이 사용 가능
3. 이름 있는 생성자
- 생성자와 달리 의미있는 이름 가질 수 있음
- 예) User.createAdminUser(), User.createGuestUser()
4. 유연성
- 반환 타입의 하위 타입 객체를 반환할 수 있음
- 동일한 시그니처로 다양한 타입의 객체 반환 가능
5. 제어권
- 객체 생성 방식을 세밀하게 제어 가능
- 싱글톤, 객체 캐싱, 객체 풀 등 구현 가능
// 일반적인 생성자 사용
User user1 = new User("John", "john@example.com", "ADMIN");
// 정적 팩토리 메서드 사용
User user2 = User.createAdminUser("John", "john@example.com");
| 이런 식으로도 사용이 가능함
public class User {
private final String name;
private final String email;
private final String type;
private User(String name, String email, String type) {
this.name = name;
this.email = email;
this.type = type;
}
// 일반 사용자 생성
public static User createRegularUser(String name, String email) {
return new User(name, email, "REGULAR");
}
// 자동 이메일 생성 사용자
public static User createAutoNamingUser(String name) {
String email = name.toLowerCase() + "@example.com";
return createRegularUser(name, email);
}
// 다른 메서드들...
}
createRegularUser 메서드의 로직을 재사용
모든 일반 사용자는 createRegularUser를 통해 생성되므로 일관성이 유지됨
일반 사용자 생성 로직이 변경되면, createRegularUser 메서드만 수정하면 됨
createAutoNamingUser는 이메일 생성 로직만 처리하고,
실제 User 객체 생성은 createRegularUser에 위임함. 이는 각 메서드의 책임을 명확히 분리하는 것임
'템플릿 메서드 패턴'의 간단한 형태로 볼 수 있습니다. 기본 동작(createRegularUser)을 정의하고, 이를 기반으로 특화된 동작(createAutoNamingUser)을 구현하는 것입니다.
- 기본 private 생성자:
- 객체 생성의 기본 메커니즘을 정의
- 외부에서 직접 접근할 수 없게 힘.
- 기본 정적 팩토리 메서드:
- private 생성자를 사용하여 객체를 생성
- 기본적인 객체 생성 로직을 캡슐화
- 확장된 정적 팩토리 메서드:
- 기본 정적 팩토리 메서드를 사용
- 추가적인 로직이나 다른 동작을 포함할 수 있음
public class User {
private User(String name, String email, String type) { ... }
// 기본 정적 팩토리 메서드
public static User createRegularUser(String name, String email) {
return new User(name, email, "REGULAR");
}
// 확장된 정적 팩토리 메서드
public static User createAutoNamingUser(String name) {
String email = name.toLowerCase() + "@example.com";
return createRegularUser(name, email);
}
// 또 다른 확장된 정적 팩토리 메서드
public static User createTemporaryUser(String name) {
String email = "temp_" + name.toLowerCase() + "@example.com";
User user = createRegularUser(name, email);
// 추가적인 임시 사용자 설정...
return user;
}
}
| createRegularUser 메서드가 static이어야 하는 이유
1. 객체 생성 목적
- 정적 팩토리 메섣의 주요 목적은 객체를 생성하는 것임
- 객체를 생성하기 위해 이미 존재하는 객체 인스턴스가 필요하지 않음
2. 인스턴스 없이 호출 가능
- static 메서드는 클래스의 인스턴스 없이 호출할 수 있음
- 객체를 만들기 위해 이미 객체가 있어야 한다면 모순이 됨
3. 다른 정적 메서드에서의 사용
- createAutoNamingUser과 createTempoaryUser 같은 다른 정적 메서드에서 createRegularUser 메서드를 호출함
- 예) User.createAdminUser(), User.createGuestUser()
4. 메모리 효율
- static 메서드는 인스턴스 메서드와 달리 각 객체마다 메모리를 차지하지 않음
5. 설계 일관성
- 모든 팩토리 메서드가 static이면 사용 패턴이 일관됨
만약 createRegularUser가 static이 아니라면:
public class User {
private User(String name, String email, String type) { ... }
private User createRegularUser(String name, String email) {
return new User(name, email, "REGULAR");
}
public static User createAutoNamingUser(String name) {
String email = name.toLowerCase() + "@example.com";
// 이 부분에서 오류 발생!
return createRegularUser(name, email);
}
}
createAutoNamingUser 메서드에서 createRegularUser를 호출할 수 없게 됨.
정적 컨텍스트에서 non-static 메서드를 직접 호출할 수 없기 때문
-> 객체 생성을 위한 팩토리 메서드는 일반적으로 static으로 선언됨
객체 생성 패턴의 표준적인 방식..
+ private User(String name, String email, String type) { ... } 는 되는데 private User createRegularUser(String name, String email) { ... } 는 왜 안됨?
사실, private User createRegularUser(String name, String email) { ... }도 문법적으로는 완전히 유효함. 인스턴스 메서드로 동작할 수 있음.
하지만..
1. 인스턴스 메서드로서 createRegularUser
- 이 메서드는 User 객체의 인스턴스 메서드가 됨
- 즉, 이미 존재하는 User 객체에서만 호출할 수 있음
2. 사용 시나리오의 제한
- 새로운 User 객체를 만들기 위해 이미 User 객체가 필요하게 됨
- 이는 팩토리 메서드의 본래 목적 (새 객체 생성)과 모순됨
3. 정적 메서드의 사용 불가
- createAutoNamingUser나 createTempoaryUser 같은 정적 메서드에서 직접 호출 할 수 없게 됨
public class User {
private User(String name, String email, String type) { ... }
private User createRegularUser(String name, String email) {
return new User(name, email, "REGULAR");
}
public static User createAutoNamingUser(String name) {
String email = name.toLowerCase() + "@example.com";
// 이 부분에서 문제 발생
// createRegularUser를 호출할 User 인스턴스가 없음
// return createRegularUser(name, email); // 이렇게 할 수 없음
// 대신 이렇게 해야 함
return new User(name, email, "REGULAR");
}
}
코드 중복: createRegularUser의 로직을 다른 static 메서드에서 직접 구현해야함
일관성 부족: 일부는 인스턴스 메서드, 일부는 정적 메서드가 됨
사용 복잡성 : 사용자가 객체 생성 방식을 이해하기 어려워짐
팩토리 메서드를 설계할 때는 일반적으로 static으로 만드는 것이 더 유용하고 일관된 방식임
이는 객체 생성의 목적에 더 부합하며, 코드의 일관성과 사용성을 높여줌
=> 결론적으로, private User createRegularUser(String name, String email) { ... }는 문법적으로는 가능하지만,
객체 생성 패턴으로서는 적절하지 않음. 정적 팩토리 메서드 패턴의 이점을 최대한 활용하려면 static으로 선언하는 것이 좋음
+ 인스턴스 메서드
인스턴스 메서드는 클래스의 객체(인스턴스)에 속한 메서드임. 이 메서드는 객체가 생성된 후에만 호출할 수 있으며, 해당 객체의 상태(필드 값)에 접근하고 조작할 수 있음.
- 인스턴스 메서드: 객체를 생성한 후 사용 가능
- 정적 메서드: 객체 생성 없이 클래스 이름으로 직접 호출 가능
public class Car {
private String model;
private int speed;
// 생성자
public Car(String model) {
this.model = model;
this.speed = 0;
}
// 인스턴스 메서드
public void accelerate() {
this.speed += 10;
System.out.println(this.model + " 속도 증가. 현재 속도: " + this.speed);
}
// 정적 메서드
public static void describeCarFeatures() {
System.out.println("자동차는 이동 수단입니다.");
}
}
// 사용 예
Car myCar = new Car("테슬라");
myCar.accelerate(); // 인스턴스 메서드 호출
Car.describeCarFeatures(); // 정적 메서드 호출
자동차를 예로 들면, 인스턴스 메서드는 각 자동차의 고유한 기능(예: 가속, 감속, 방향 전환)과 같습니다. 이러한 기능은 각 자동차마다 다르게 작동할 수 있으며, 특정 자동차가 존재해야만 사용할 수 있습니다.
'Java' 카테고리의 다른 글
파라미터 타입이 다른 공통 로직 모듈화 - 제네릭 (0) | 2024.12.11 |
---|---|
파라미터 타입이 다른 공통 로직 모듈화 - 인터페이스 (0) | 2024.12.11 |
jsp (0) | 2024.12.10 |
Java Stream - Stream, map, collect()로 리스트 객체 가공 (0) | 2024.10.29 |
일급 컬렉션 (0) | 2024.10.21 |