오늘의 나보다 성장한 내일의 나를 위해…
Sigleton Pattern
싱글턴 패턴: 어떤 클래스에서 만들 수 있는 인스턴스 수를 하나로 제한하는 패턴이다.
다음과 같은 조건을 충족하는 개체에 적합하다.
- 프로그램 실행 중에 최대 하나만 있어야 할 경우
- 전역적으로 접근이 가능한 개체여야 할 경우
딱 하나(single)만 존재해야만 해서 이름이 싱글턴인 것이다.
일부 사람들은 static를 싫어한다. 그 이유는 전역 변수 같아 보이고 개체가 아니기 때문이다. 하지만 싱글턴은 이러한 비판을 해결하는 패턴이다. 그러면서도 OO(개체지향)에서 전역 변수 및 전역 함수를 만드는 법이다.
싱글턴 패턴은 기본적으로 private 생성자를 사용한다. 그럼으로 static 메서드를 통해서만 개체를 얻어올 수 있는데 바로 getInstance()메서드이다. 개체가 없는 경우 개체를 생성 후 static 변수에 저장하고 static 변수에 저장된 개체를 반환하게 된다.
이미 개체가 있는 경우 static 변수에 저장되어 있는 개체를 반환한다. 아래 코드를 보자
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
Main
Singleton instance0=Singleton.getInstance();
Singleton instance1=Singleton.getInstance();
System.out.println("same Object? "+(instance0==instance1));
위는 싱글턴 패턴의 간단한 예이다. 밑에 코드는 Math 클래스를 사용한 싱글턴 패턴의 예이다.
Math Class
public class Math{
private static Math instance;
private Math(){
}
public static Math getInstance(){
if(instance==null){
instance=new Math();
}
return instance;
}
public int abs(int n){
return n<0?-n:n;
}
(...)
}
Main
int absValue=Math.abs(-2); //컴파일 오류가 발생
Math math=Math.getInstance();
int minValue=math.min(-2,1);
int maxValue=Math.getInstance().max(3,100);
여기서 중요하게 봐야할 점은 싱글턴을 만다는데 사용한 static이다. Math Class에서 변수를 만들때도 static를 사용했고 getInstance() 메서드를 만드는데도 static를 사용했다. 그리고 나머지는 static이 아니라 일반 메서드이다.
위 클래스는 메서드만 있어 별로 좋은 예는 아니다. 이번에는 상태도 가지는 다른 싱글턴을 보자.
프로그램 설정 읽기 - Configuration 싱글턴
- 설정은 프로그램 실행 중 하나만 존재한다.
- 프로그램 창의 위치와 크기를 기억한다.
- 파일에 저장하거나 파일로부터 로딩이 가능하다.
위 클래스 다이어그램을 보면 밑줄 친 것은 static 선언을 했다는 것을 알 수 있다. 그리고 멤버 변수들은 static이 아니다.
Configuration Class
public class Configuration{
private static Configuration instance;
private int x;
private int y;
private int width;
private int height;
private Configuration(){
}
public static Configuration getInstance(){
if(instance==null){
instance=new Configuration();
}
return instance;
}
public int getX(){
return this.x;
}
public void setX(int x){
this.x=x;
}
public int getY(){
return this.y;
}
public void setY(int y){
this.y=y;
}
public int getWidth(){
return this.width;
}
(... 생략)
public void save(String filename){
//설정을 파일로 저장
}
public void load(String filename){
//파일로부터 설정 읽기
}
}
Main
//프로그램 실행 후 파일로부터 설정을 읽어 옴
Configuration config=Configuration.getInstance();
config.load("settings.csv");
//창 위치 바뀔 때, x와 y를 설정함
Configuration config=Configuration.getInstance();
config.setX(windowX);
config.setY(windowY);
//프로그램 종료 시, save()를 이용해서 변경 사항을 파일에 저장
config.save();
그런데 결과적으로 static으로 만드는 것과 같지 않나? 물론 Configuration 예에서는 그렇다. 그런데 두 방법(static vs 싱글턴) 간에 다른 점은 분명히 있다.
static으로는 못하는 일
- 다형성을 사용할 수 없다.
- 시그내처를 그대로 둔 채 멀티턴(multiton) 패턴으로 바꿀 수 없다.
- 개체의 생성 시점을 제어할 수 없다
- Java의 static은 프로그램 실행 시에 초기화됨
- 단, 싱글턴을 사용해도 제어에 어려움이 있음
싱글턴 생성 시 인자가 필요한 경우
public class GraphicsResourceManager{
(...)
public static GraphicsResourceManager getInstance(FileLoader loader, GraphicsDevice gfxDevice){
if(instance==null){
instance=new GraphicsResourceManager(loader, gfxDevice);
}
return instance;
}
}
// 프로그램 시작 시
GraphicsResourceManager.getInstance(loader, gfxDevice);
//다른 클래스에서
GraphicsResourceManager.getInstance(???,???);
한 예로 GraphicsResource는 화면에 이미지, 3차원 모델등을 보여주는 것이라고 해보자. 그러면 실제 화면에 그림을 그릴 장치(GraphicsDevice gfxDevice)가 필요하고 파일을 로딩해야 하니까 파일 로딩에 관한 개체(FileLoader loader)도 필요할 것이다.
그럼 위와 같이 매개변수를 전달해서 만들어줘야 한다. 물론 loader, gfxDevice를 먼저 초기화하고 매개변수로 넣어줄 수 있겠지만 주석에서 명시한 것과 같이 다른 클래스에서 getInstance()로 가져온다고 해보자. 그럼 사용하지도 않을 매개변수를 넣어줘야 하는데 다른 클래스에 loader와 gfxDevice가 들어가 있지 않을 가능성이 높다.
그럼 다른 클래스 안에 저 매개변수가 없다면 어떻게 할 것인가? 그것을 해결하기 어렵다.
싱글턴의 변형
- 현재의 구현으로는 표현이 어렵다.
- 따라서 실무에서는 다른 변형을 사용하기도 한다.
- 디자인 패턴은 그저 가이드 라인일뿐
- 필요에 따라 변형해서 사용하는 것도 괜찮다.
위 예제를 조금 변형해보자.
public class GraphicsResourceManager{
private static GraphicsResourceManager instance;
private GraphicsResourceManager(FileLoader loader, GraphicsDevice gfxDevice){
(...)
}
public static void createInstance(FileLoader loader, GraphicDevice gfxDevice){
assert (instance==null) : "do not create instance twice";
instance = new GraphicsResourceManager(loader, gfxDevice);
}
public static void deleteInstance(){
assert (instance != null) : "no instance to delete";
instance = nulll;
}
public static GraphicsResourceManager getInstance(){
assert (instance != null) : "no instance was created before get()";
return instance;
}
}
위는 createInstance와 getInstance가 분리된 형태다.
프로그램 실행 시 getInstance()가 아니라 createInstance()를 호출하는 형태다.
GraphicsResourceManager.createInstance(loader, gfxDevice);
인스턴스가 필요할 때는 매개변수가 없는 getInstance()를 호출해준다.
GraphicsResourceManager gfxManager=GraphicsResourceManager.getInstance();
더 이상 사용하지 않는 싱글턴 인스턴스를 삭제한다.
GraphicsResourceManager.deleteInstance();
바뀐 getInstance() 함수는 싱글턴 인스턴스가 null일 경우를 대비해서 어서트(assert)를 추가했다. 그리고 createInstance()가 먼저 호출됐다는 가정으로 돈다.
public static GraphicsResourceManager getinstance(){
asswert (instance != null) : "no instance was created before get()";
return instance;
}