디자인 패턴

싱글톤 패턴(2) - 싱글톤 패턴을 깨뜨리는 방법, 안전하고 단순하게 구현하는 방법

깊게 생각하고 최선을 다하자 2022. 8. 31. 03:51

1) 싱글톤 패턴을 깨뜨리는 방법

- 자바에서는 싱글톤 패턴을 깨뜨리는 2가지 방법이 있습니다.

  첫째는, 리플렉션 사용하기

  둘째는, 직렬화 & 역직렬화 사용하기 입니다. 

 

(1) 리플렉션 사용하기

- 자바에서는 리플렉션을 사용해서 싱글톤 패턴을 깨뜨릴 수 있습니다. 

public class App{

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException {
         Settings settings = Settings.getInstance();
         
         Constructor<Settings> constructor = Settings.class.getDeclaredConstructor();
         constructor.setAccessible(true);
         Settings settings1 = constructor.newInstance();
    
         System.out.println(settings == settings1);
    }
}

// 실행 결과
false

 

(2) 직렬화 & 역직렬화 사용하기

- 자바에서는 객체를 파일 형태로 저장(=직렬화)했다가, 읽어 들이는(=역직렬화)것이 가능합니다. 

   직렬화 및 역직렬화가 가능하려면, 해당 클래스에 Serializable 인터페이스를 구현해야 합니다.

   역직렬화를 할 때는, 생성자로 객체를 만들어주므로, 다른 객체가 만들어집니다. 

public class Settings implements Serializable{

    private static volatile Settings instance;
    
    private Settings(){}
    
    private static class SettingsHolder{
    	private static final Settings INSTANCE = new Settings();
    }
    
    public static Settings getInstance(){
    	return SettingsHolder.INSTANCE;
    }
}
public class App {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException {
        Settings settings = Settings.getInstance();
    
        try(ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))){
            out.writeObject(settings);
        }    
        
        try(ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))){
            settings1 = (Settings)in.readObject();
        }
        
        System.out.println(settings == settings1);
    }
}

// 실행 결과
false

- 역직렬화 같은 경우는, readResolve라는 시그니처를 갖고 있으면, 역직렬화를 할 때 사용합니다. 

  readResolve메소드 안에 getInstace 메소드를 넣어두면, 결과적으로 getInstance 메소드를 사용합니다. 

  이렇게 하면 동일한 객체를 얻을 수 있습니다. 

  반면, 리플렉션은 이러한 대응이 불가능합니다. 

public class Settings implements Serializable{

    private static volatile Settings instance;
    
    private Settings(){}
    
    private static class SettingsHolder{
    	private static final Settings INSTANCE = new Settings();
    }
    
    public static Settings getInstance(){
    	return SettingsHolder.INSTANCE;
    }
    
    protected Object readResolve(){
        return getInstance();
    }
}

 

2) 안전하고 단순하게 구현하는 방법

- 자바가 제공하는 enum을 사용해서 싱글톤 패턴을 구현할 수도 있습니다. 

   enum을 사용하면 리플렉션에 안전한 코드가 됩니다. 

   enum 클래스는 리플렉션에서 newInstance를 할 수 없도록 막아놨기 때문입니다. 

   반면, 인스턴스를 미리 만들어야 하고, 상속을 사용하지 못한다는 단점이 있습니다. 

public enum Settings{

   INSTANCE;
   
   private Settings(){
   }
   
   private Integer number;
   
   public Integer getNumber(){
       return number;
   }
   
   public void setNumber(Integer number){
      this.number = number;
   }

   
}

- 또한, enum은 기본적으로 Serializable 인터페이스를 구현하며, 직렬화 & 역직렬화에 안전합니다. 

 

참고

백기선 코딩으로 학습하는 GoF의 디자인 패턴