[부트캠프] TIL - 정적 팩토리 생성자
정적 팩토리 메소드 (생성자)는 객체를 생성할 때 사용하는 메소드.
보통 객체를 생성할때는 생성자를 사용하여 new 키워드를 사용하여 직접 생성을 하지만.
정적 팩토리 메소드는 클래스 안에 정적(static) 메소드를 선언하여 객체를 생성하고 반환(return)하는 방식입니다.
팀원의 코드에서 발견한 .of() 메소드 또는 .getInstance()가 주로 이런 방식에 해당되고
장점으로는
객체 생성의 유연성을 높이고, 클래스 설계의가독성이 좋아진다고합니다,
간단한 예시
public class User {
private String name;
private int age;
private User(String name, int age) {
this.name = name;
this.age = age;
}
public static User createUser(String name, int age) {
return new User(name, age);
}
}
결국 그냥 어디에나 있는 클래스의 메소드 안에서 결국에는 생성자를 호출하는방식으로.
어차피 생성자를 호출하는거 굳이 메소드안에 집어넣을 필요가 있나 라는 생각이 먼저들엇다.
그치만 장점으로는 메소드명을 직접 작성할 수 있기 때문에.
메소드명이 클래스명으로 고정된 생성자와는 다르게 직관적으로 어떤 상황에서 객체를 생성하는지 명확하게 알 수 있다는 장점이 있습니다.
동작방식이 생성자에 들어갈 매개변수를 메소드로 하나씩 받아오며 마지막에 통합빌드하며 생성하는 방식이라.
클래스에 선택적 매개변수가 많은 경우에 객체를 생성하는 입장에서 편한 장점이 있습니다.
그리고 객체가 생성되는 로직이 메소드 안에 들어가기 때문에, 캡슐화와 유연성을 높이는데 도움이 됩니다.
그 외에도 싱글톤 패턴이나 불변객체 패턴 구현할 경우에도 사용이 가능합니다.
예시
public class DatabaseConnection {
private static final DatabaseConnection INSTANCE = new DatabaseConnection();
private DatabaseConnection() {
// 생성자
}
public static DatabaseConnection getInstance() {
return INSTANCE; // 싱글톤 객체 반환
}
}
반환타입의 유연성
클래스가 인터페이스를 구현할 경우, 정적 팩토리 메소드 내부에서 인터페이스 타입을 반환하면서
구체적인 객체를 선택하여 생성자를 호출해주는 식으로 구현이 가능.
정적 팩토리 메소드의 단점:
- 서브클래스 확장이 어렵다:
- 정적 팩토리 메소드를 사용하면 클래스의 생성자가 private 또는 **protected**로 선언될 때가 많습니다. 이 경우 서브클래스 확장이 제한될 수 있습니다.
- API 학습이 필요:
- 생성자는 누구나 직관적으로 사용하지만, 정적 팩토리 메소드의 경우 어떤 메소드가 객체를 반환하는지를 알아야 하기 때문에, API 문서를 참고해야 할 때가 많습니다.
- 메소드가 많아질 수 있다:
- 객체 생성 방식에 따라 여러 가지 정적 팩토리 메소드가 만들어질 수 있는데, 이로 인해 클래스의 메소드 수가 많아지면 가독성이 떨어질 수 있습니다.
언제 정적 팩토리 메소드를 사용해야 할까?
- 객체 생성에 복잡한 로직이 있는 경우: 단순한 객체 생성이 아니라 여러 가지 조건에 따라 객체를 생성해야 할 때 사용합니다.
- 싱글톤이나 불변 객체가 필요한 경우: 동일한 상태의 객체를 여러 개 만들 필요가 없을 때, 객체 재사용을 위해 사용됩니다.
- 인터페이스나 추상 클래스 타입을 반환하고 싶을 때: 상위 타입으로 여러 가지 하위 타입을 반환하는 유연성을 제공할 수 있습니다.
- 객체 생성의 제어가 필요한 경우: 객체 생성을 완전히 제어하고, 객체 상태를 보존하거나 캡슐화할 필요가 있을 때 사용합니다.
결론:
정적 팩토리 메소드는 객체 생성의 유연성을 제공하는 동시에 캡슐화와 가독성을 높일 수 있는 유용한 기법입니다. 이를 통해 객체 생성을 더 유연하고 명확하게 제어할 수 있으며, 특히 싱글톤 패턴, 불변 객체, 서브클래스 반환 같은 경우에 매우 유리합니다. 하지만 남용하게 되면 코드의 복잡성이 증가할 수 있기 때문에, 상황에 맞게 신중하게 사용하는 것이 중요합니다.
유사한거로
Builder()와 .of()가 있는데
public class User {
private String username;
private String password;
private String email;
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
// Getter 메소드는 생략
public static class Builder {
private String username;
private String password;
private String email;
public Builder() {
}
public Builder(User user) {
this.username = user.username;
this.password = user.password;
this.email = user.email;
}
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public User build() {
return new User(username, password, email);
}
}
}
Lombok을 사용한 어노테이션을 사용하는 방식과 이렇게 직접 선언하는 방식이 따로 있다고 합니다.
저렇게 선언하면 실제 사용시에는
public class Main {
public static void main(String[] args) {
User user = User.builder()
.username("홍길동")
.password("password1234")
.email("hong@example.com")
.build();
}
}
.builder()로 호출하고 각각의 변수들을 주입하기 위한 메소드를 호출하며 진행하고 마지막으로 .build()를 호출하며
생성한 객체를 반환하게합니다.
Lombok의 @Builder 어노테이션을 사용하면 간단하게 사용도 가능.