Java

정적 팩토리 메서드

99duuk 2024. 10. 21. 10:48

| 정적 팩토리 메서드

는 객체를 만드는 특별한 방법임

일반적인 생성자 대신, 클래스 안에 객체를 만드는 전용 메서드를 만들어 사용함.

이 메서드는 이름을 가질 수 있어서 객체가 어떤 목적으로 만들어지는지 더 명확하게 알 수 있음

 

 

레고 세트를 생각해보세요. 보통은 설명서를 따라 직접 조립하지만(일반 생성자),
때로는 이미 조립된 부분(예: 우주선 조종석)을 구매할 수 있습니다.
이렇게 미리 조립된 부분을 제공하는 것이 정적 팩토리 메서드와 비슷합니다.
목적에 맞게 미리 구성된 객체를 제공받는 것이죠.

 

 

 

| 정적 팩토리 메서드는 객체 생성을 캡슐화하는 디자인 패턴임

- 이름을 가질 수 있어 생성 목적을 명확하게 표현할 수 있음

- 호출될 때마다 새 객체를 생성할 필요가 없음 (캐싱, 객체 풀링 등 가능)

- 반환 타입의 하위 타입 객체를 반환할 수 있어 유연성이 높음

- 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 없음

- 정적 메서드이므로 상속할 수 없다는 단점

 

 

 

| 주로 사용되는 네이밍 컨벤션

- 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();  // 정적 메서드 호출

 

 

자동차를 예로 들면, 인스턴스 메서드는 각 자동차의 고유한 기능(예: 가속, 감속, 방향 전환)과 같습니다. 이러한 기능은 각 자동차마다 다르게 작동할 수 있으며, 특정 자동차가 존재해야만 사용할 수 있습니다.