[기술공부]정적 팩토리 메서드

allnight5·2023년 4월 29일
0

기술공부

목록 보기
22/33

목차
정적 팩토리 간단 설명
생성자만을 이용하는것과 어떤 차이가 있는가?
호출할 때마다 인스턴스를 새로 생성하지 않아도 된다.
반환 타입이 유연하다.
하위 자료형 객체를 반환할 수 있다
하위 클래스 생성 제한
객체 생성을 캡슐화 할 수 있다
정적 팩토리 메서드의 단점.
정적 팩토리 메서드 네이밍 컨벤션

정적 팩토리 간단 설명

목차로 이동
정적 팩토리 메서드의 팩토리는 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다.
생성자 대신에 메서드로 객체를 생성하는 방식이다.

  • 캡슐화 해서 객체 생성하는 디자인 패턴
  • 생성자를 직접 사용하는 것을 제한할 수 있다.

(23.05.09 11:00)
예시들 잘못 되어 있는것을 알고 수정

올바른 예시

public class call{
	private String region;
    private String phonenumber;
    
    private call(String region, String phonenumber){
    	this.region = region;
        this.phonenumber = phonenumber;
    }
    
    public static call callRegionFrom(String region){
    	return new call(region, "who");
    }
}

잘못된 예시

public class call{
	private String region;
    private String phonenumber;
    
    public call(String region, String phonenumber){
    	this.region = region;
        this.phonenumber = phonenumber;
    }
    
    public static call callRegionFrom(String region){
    	return new call(region, "who");
    }
}

잘못된 예시와 올바른 예시 두개를 봤을때
무슨차이가 있는가??

생성자의 접근제한자의 차이가 있습니다.
잘못된 부분은 public이고 올바른 부분은 private입니다.
public으로 해두면 외부에서 call 객체를 생성할 수 있으므로,
다른 스레드에서 동시에 객체를 생성할 수 있다.
이 경우, 여러 스레드가 동시에 같은 call 객체를 참조할 수
있으므로 동시성 문제가 발생할 가능성이 있습니다.

정적 팩토리 메소드를 이용한 객체 생성을 만들때 접근제한자를
신경써서 무의식적으로 public이 아닌것으로 만들기 바랍니다.
(만드는 사람에 따라서 public으로 사용하면서 스레드 접근 제한하고 할 수 도 있다.)[스레드 동기화 기술 등]

생성자만을 이용하는것과 어떤 차이가 있는가?

목차로 이동

  • 생성자의 클래스 이름이 아닌 다른 이름으로 사용할 수 있다.

정적 팩토리 메서드는 목적에 맞게 명확한 이름을 직접 지정할 수 있다.
반면에 생성자는 클래스 이름만 사용하기 때문에, 동일한 클래스에 여러 생성자가 있는 경우 구분하기 어렵다.

public class Command{
	private String rule;
   private String order;
   // private 생성자
   private Command(String rule, String order){
   	this.rule = rule;
       this.order = order;
   }
   // 정적 팩토리 메서드 (매개변수 하나는 from 네이밍)
   public static Command RuleFrom(String rule) {
       return new Command(rule, "rule");
   }

   public static Command OrderFrom(String order) {
       return new Command("order", order);
   }
   // 정적 팩토리 메서드 (매개변수 여러개는 of 네이밍)
   public static Command Of(String rule, String order) {
       return new Command(rule, order);
   }
}
public static void main(String[] args) {
	Command firstRule = Command.RuleFrom("admin");
	Command firstCommand = Command.OrderFrom("stop");
	Command firstCommand = Command.Of("admin","start");
}

정적 팩토리 메서드를 사용하면 해당 생성의 목적을 이름에 표현할 수 있어 가독성이 좋아지는 효과가 있다.

호출할 때마다 인스턴스(객체)를 새로 생성하지 않아도 된다.

목차로 이동
인스턴스를 재사용할 수 있도록 만들어 메모리 사용량을 줄일 수 있다.

예시1

class call{
	private static call instance;
    private call(){}
    public static synchronized call getInstance() {
        if (instance == null) {
            instance = new call();
        }
        return instance;
    }
}

이런식으로 하나의 인스턴스만 생성해서 생성 되었는지 확인후 하는 방법도 있다.

synchronized 동기화(한번의 하나의 스레드만 공유자원에 접근할수 있게하는것)를 해준이유가 무엇일까?
멀티스레드 환경에서 인스턴스를 생성하기 전에 null체크를 통과하는 스레드가 2개라고 생각해보자
그럼 2개의 인스턴스가 생성되어 원치않는 동작이 일어날수있다.
그러니 동기화를 해주어 이러한 문제를 방지하는것이다.

다른 방법으로는 Lock과 Condition 클래스를 사용하여 synchronized 키워드를 이용한 동기화보다 더 세밀하게 동기화를 제어할 수 있다.

아래는 다른방법으로 2개의 스레드가 통과하지 않게하는 방법이다.

예시 2

class call {
	private static volatile call instance;
    private call(){}
    public static call getInstance() {
        if (instance == null) {
            synchronized (call.class) {
                if (instance == null) {
                    instance = new call();
                }
            }
        }
        return instance;
    }
}

Double-Checked Locking이라는 기법이다.

volatile 키워드를 사용하여 객체 인스턴스에 대한 가시성을 보장하고, 두 번째 null 체크를 추가하고 synchronized 블록으로 감싸여 동시성 문제를 해결하는 방법이다.

이런 방법말고도
enum과 같이 자주 사용되는 요소의 개수가 정해져있다면 해당 개수만큼 미리 생성해놓고 조회(캐싱)할 수 있는 구조로 만들수 있다.

enum을 만들어서 할 수도 있지만, 내용이 숫자라면 반복문을 통해 인스턴스를 생성할 수도 있다.

매번 인스턴스를 새로 생성하지 않고
기존에 만들어둔 인스턴스나, 생성한 인스턴스를 캐싱해서 재활용 해서 불필요한 객체 생성을 방지하고 성능이 향상된다.

인스턴스를 통제하면 싱글톤 패턴, 플라이웨이트 패턴을 적용할 수 있다.

1. 싱글톤 (Singleton) 패턴

싱글톤 클래스에 전체에서 인스턴스가 하나만 생성하고, 이 인스턴스는 전역적으로 엑세스할 수 있다.

2. 플라이웨이트 (Flyweight) 패턴

객체를 재사용하여 메모리 사용량을 최소화한다.
인스턴스 통제 클래스는 객체 풀에 인스턴스를 보관하고, 요청 시 동일한 객체를 반환해서 불필요한 객체 생성을 막는다.

따라서 인스턴스 통제 클래스는
인스턴스를 효율적으로 재사용하거나 제한하여 시스템 리소스를 절약할 수 있고,
동일한 인스턴스에 대해 동일성(identity)를 보장해서 객체 비교에 효율적이라는 장점이 있다.

반환 타입이 유연하다.

목차로 이동

@Getter
public class Command{
	private String rule;
   private String order;
   // private 생성자
   private Command(String rule, String order){
   	this.rule = rule;
       this.order = order;
   }  
   public static SaveInput newSaveInputCommand(Command command) { 
       return new SaveInput(command);
   }
   
   public static Optional newOtherTypeCommand(Command command) { 
       switch (command.getOrger()){
           case "OUT":
               return new OutputCommand(command);
           case "IN":
               return new InputCommand(command);
           default : 
           	return new defalutCommand(command);
       }
   }
}

이런식으로 다른 형태의 반환타입으로도 반환할 수 있다.
예시로 Optional을 사용하긴 했는데 실제로 사용할때는 다양한 예외상황이 발생할 수 있으니 충분한 테스트와 예외처리를 생각하시고 사용해야합니다.

하위 자료형 객체를 반환할 수 있다.

목차로 이동
하위 자료형 객체를 반환하는 정적 팩토리 메서드의 특징은 상속을 사용할 때 확인할 수 있다.

public interface Animal {
    void makeSound();
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Weeee!");
    }
}

public class AnimalFactory {
    public static Animal createCat() {
        return new Cat();
    }
}

하위 클래스 생성 제한

목차로 이동
기본 클래스에서 파생된 클래스(하위 클래스)의 인스턴스 생성을 제한할 수 있다.

public class ImmutableString {
    //final 을 사용하면 변수는 초기화 후 변경할 수 없게 만들어 불변성을 보장한다.
    private final String value;
	
    //private 접근 제한자를 사용하여 외부에서 직접 생성자를 호출할 수 없게 한다.
    private ImmutableString(String value) {
        this.value = value;
    }
	
    //정적 팩토리 메소드: ImmutableString 인스턴스를 생성하고 반환
    public static ImmutableString fromString(String value) {
        return new ImmutableString(value);
    }
	
    //getter: 객체의 값을 읽을 수 있지만, 수정할 수 없다
    public String getValue() {
        return value;
    }
}

// 클라이언트 코드
ImmutableString myString = ImmutableString.fromString("Hello, world!");

클래스가 final로 선언되면, 그 클래스는 상속될 수 없으며 하위 클래스를 생성할 수 없다.
생성자가 private이거나 protected로 선언되면, 외부에서 직접 객체를 생성할 수 없으므로 하위 클래스 생성이 제한된다.

예시로 ImmutableString 클래스는 불변 (immutable) 문자열 객체인 String 타입의 value 필드를 가진다.

*불변 객체: 한 번 생성되면 그 상태를 변경할 수 없다.

따라서 이렇게 생성된 객체는 불변 immutable 하므로, 초기화 후에는 내부의 문자열 값이 변경되지 않는다.

객체 생성을 캡슐화 할 수 있다

목차로 이동

 public class Command{
 	private String rule;
    private String order;
    // private 생성자
    private Command(String rule, String order){
    	this.rule = rule;
        this.order = order;
    }
    public static Command from(Input input){
    	return new Command(input.getWho(), input.getIndata());
    }
}
input -> command로 변환
// 정적 팩토리 메서드를 쓴 경우
Command command = Command.from(input); 
// 생성자를 쓴 경우
Command command = new Command(input.getWho(), input.getIndata()); 

만약 정적 팩토리 메서드를 쓰지 않고 DTO로 변환한다면 외부에서 생성자의 내부 구현을 모두 드러낸 채 해야할 것이다.
도메인에서 “객체 생성”의 역할 자체가 중요한 경우라면 정적 팩토리 클래스를 따로 분리하는 것도 좋은 방법이 될 것이다. 다만 팩토리 메서드만 존재하는 클래스를 생성할 경우 상속이 불가하다는 단점이 있으니 참고하여 사용하길 바란다.

정적 팩토리 메서드의 단점.

목차로 이동

1. 가독성이 낮을 수 있다.

정적 팩토리 메서드는 일반적으로 생성자와 이름이 유사하므로, 코드의 가독성이 낮아질 수 있습니다. 이를 방지하기 위해서는 메서드 이름을 충분히 명확하고 직관적으로 지정해야 합니다.

2. 상속을 제한할 수 있다.

정적 팩토리 메서드는 상속을 제한할 수 있는 점이 있습니다. 생성자는 하위 클래스에서 사용할 수 있지만, 정적 팩토리 메서드는 오직 정의된 클래스에서만 사용 가능합니다. 이를 해결하기 위해서는 생성자를 노출시키거나 팩토리 메서드를 오버라이딩할 수 있습니다.

3. 문서화가 어려울 수 있다.

정적 팩토리 메서드는 일반적으로 API 문서에 표시되지 않기 때문에, 사용자들이 해당 메서드를 찾기 어려울 수 있습니다. 이를 방지하기 위해서는 명확하게 문서화를 해주어야 합니다.

4. 클래스 외부에서 사용할 수 없을 수 있다.

정적 팩토리 메서드를 사용하여 객체를 생성할 때에는 생성자처럼 클래스 외부에서 직접 접근할 수 없습니다. 따라서 이를 사용하는 경우, 클래스의 일부분이 숨겨져 있을 수 있으며, 사용자들이 해당 클래스의 내부를 이해하기 어려울 수 있습니다.

5. 유연성이 제한될 수 있다.

정적 팩토리 메서드는 객체를 생성하는 데 있어서 매개변수를 더 이상 추가할 수 없다는 점이 있습니다. 이는 클래스의 생성자와 달리, 유연성이 제한될 수 있다는 것을 의미합니다. 따라서 이를 방지하기 위해서는 메서드 오버로딩을 사용하거나, Builder 패턴 등의 다른 객체 생성 방식을 고려해 볼 수 있습니다.

6. 정적 팩토리 메서드만 제공하는 클래스는 생성자가 없어 보일 수 있다.

이러한 클래스는 개발자가 객체를 생성할 때, 생성자 대신 정적 팩토리 메서드를 사용해야 한다는 것을 명시적으로 알려주지 않으면 혼란스러울 수 있습니다. 따라서 문서화나 주석 등으로 명확히 표시해주어야 합니다.
이러한 문제점을 해결하려면, 생성자와 정적 팩토리 메서드를 함께 제공하는 것이 좋을 수 있습니다

정적 팩토리 메서드 네이밍 컨벤션

목차로 이동

  • from : 하나의 매개 변수를 받아서 객체를 생성
  • of : 여러개의 매개 변수를 받아서 객체를 생성
  • getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • newInstance | create : 새로운 인스턴스를 생성
  • get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.

정리해보기

장점과 단점을 살펴보면
장점이 단점이 될 수 있다는 설명이다. 모든 장점이 단점이 될 수 있으니
작성할때 조심히 작성해야하며 캡슐화 형태로 짜서 문서화하여 주석을 작성해두며 변경시 주석도 변경을 하는 것이 좋다. 주석을 최신화 하는게 귀찮다면 문서화 하지 말고 주석을 작성하지 않는것이 좋을 것이다.

참조사이트

Tecoble
별별코딩
인파
ynzu

profile
공부기록하기

0개의 댓글