chjs93 2020. 12. 31. 01:01

인스턴스를 한 개만 만들기

 

본 글은 'java 언어로 배우는 디자인 패턴 입문' 을 바탕으로 기재한 내용입니다.


프로그램을 실행할 때 보통은 많은 인스턴스가 실행이된다. 예를 들어 문자열을 표시해야 할 때 사용하는 java.lang.String 클래스는 문자열 1개에 대해서 1개가 생성이 되기 때문에 문자열이 1천개 등장하는 프로그램이라면 1천개의 인스턴스가 만들어진다.

 

하지만 클래스의 인스턴스가 단 하나만 필요한 경우도 있다. 시스템 안에서 밖에 존재하지 않는 것을 프로그래밍 하고 싶을때이다.

예를들어.

컴퓨터 자체를 표현한 클래스, 현재 시스템 설정을 표현한 클래스, 윈도우 시스템을 표현한 클래스 등등 이 있을 것이다.

 

물론 프로그래머가 주의 하여 new Class() 로 1회 실행하여 1개의 인스턴스를 만들게 할 수 있지만

지정한 클래스의 인스턴스가 1개 밖에 존재하지 않는 것을 '보증' 하고 싶을 때

인스턴스가 1개밖에 존재하지 않는 것을 프로그램 상에서 표현하고 싶을 때

사용할 수 있는 패턴이

 

Singleton 패턴 이다.

(요소를 1개밖에 가지고 있지 않는 집합)

 

예제 프로그램

인스턴스가 1개만 존재하는 클래스

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {
        System.out.println("인스턴스 생성함.");
    }

    public static Singleton getInstense() {
        return singleton;
    }
}

Singleton 클래스의 생성자는 private 로 되어 있다. Singleton 클래스 외부에서 생성자의 호출을 금지하기 위해서이다.

만약 new Singleton() 으로 외부에 있더라도 컴파일 시에는 에러가 난다.

작성자의 실수를 방지하기 위한 보증이라고 생각해도 될 것이다.

 

getInstense() : Singleton 클래스의 유일한 인스턴스를 가져올 메소드이며 static 메소드이다!

 

 

실행

public class Main {
    public static void main(String[] args) {
        System.out.println("Start");
        Singleton obj1 = Singleton.getInstense();
        Singleton obj2 = Singleton.getInstense();
        if (obj1 == obj2) {
            System.out.println("obj1, obj2 동일한 인스턴스");
        } else {
            System.out.println("obj1, obj2 동일하지 않은 인스턴스");
        }
        System.out.println("End");
    }
}


/**
 * 결과
 */
 
// Start
// 인스턴스 생성함.
// obj1, obj2 동일한 인스턴스
// End
 

obj1 과 obj2 에 같은 인스턴스를 담고 있는지 확인하는 Main 클래스이다.

 

유일한 인스턴스는 언제 생성?

예제 프로그램 Main 클래스에서는 프로그램 실행 후 최초로 getInstance 메소드를 호출했을 때 Singleton 클래스는 초기하 도니다. 이때 static 필드의 초기화가 이루어지고 유일한 인스턴스가 만들어진다.

 

패턴의 등장인물

* Singleton의 역할
Singleton 패턴에는 Singleton의 역할만 존재한다. Singleton 역할은 유일한 인스턴스를 얻기 위한 static 메소드를 가지고 있다. 
해당 메소드는 언제나 동일한 인스턴스를 반환한다.

 

 

왜 Singleton 패턴?

제한할 필요가 있다.

인스턴스 수를 제한하는 방법을 통해 복수의 인스턴스가 존재할때 인스턴스 서로간의 영향을 줄이고 뜻하지 않는 버그의 가능성을 줄이며 인스턴스 1개의 전제조건 안에서 프로그래밍 할 수 있기 때문이다.

 

 

보강

문제1

다음 TicketMaker 클래스는 getNextTicketNumber 메소드를 호출 할 때마다 1000, 1001, 1002 를 호출 하는데 싱글톤 패턴을 적용하여 하나의 인스턴스만 생성하게 만들어라.

public class TicketMaker {
    private int ticket = 1000;

    public int getNextTicketNumber() {
        return ticket++;
    }
}

public class TicketMakerMain {
    public static void main(String[] args) {
        System.out.println("Start");

        for (int i = 0; i < 10; i++) {
            System.out.println(i + ":" + TicketMaker.getInstense().getNextTicketNumber());
        }

        System.out.println("End");
    }
}

 

 

문제2

인스턴스 개수가 3개로 한정되어 있는 Triple 을 만드시오

(비슷한 클래스로 strategy 패턴의 Hand 클래스에서도 나옴)

 

public class Triple {
    private static Triple[] triple = new Triple[] { new Triple(0), new Triple(1), new Triple(2) };
    private int id;

    private Triple(int id) {
        System.out.println("The instance " + id + " is created");
        this.id = id;
    }

    public static Triple getInstance(int id) {
        return triple[id];
    }

    public String toString() {
        return "[Triple id=" + id + "]";
    }

    public static void main(String[] args) {
        System.out.println("Start");
        for (int i = 0; i < 9; i++) {
            Triple triple = Triple.getInstance(i % 3);
            System.out.println(i + ":" + triple);
        }
        System.out.println("End");
    }
}

 

주의 : triple 필드에 static 을 사용하지 않으면 무한루프가 되어 실행시 에러(스택오버플로우) 가 발생함

위코드가 무한루프가 되지 않는 것은 triple 필드가 인스턴스 필드가 아니라, 클래스 필드이기 때문이다.

(Triple 클래스의 triple 필드를 Triple 인스턴스에서 초기화 하고 있음)

 

 

문제3

다음 코드는 엄밀하게 말해 Singleton 패턴이 아니다 이유는?

public class NotSingleton {
    private static NotSingleton singleton = null;

    private NotSingleton() {
        System.out.println("인스턴스 생성");
    }

    public static NotSingleton getInstence() {
        if (singleton == null) {
            singleton = new NotSingleton();
        }
        return singleton;
    }
}

다수의 쓰레드에서 NotSingleton.getInstence() 메소드가 호출 되었을 때 다수의 인스턴스가 생성될 가능성이 있기 때문이다.

 

public class NotSingleton {
    private static NotSingleton singleton = null;

    private NotSingleton() {
        System.out.println("인스턴스 생성");
        slowdown();
    }

    public static synchronized NotSingleton getInstence() {
        if (singleton == null) {
            singleton = new NotSingleton();
        }
        return singleton;
    }

    private void slowdown() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {

        }
    }
}

synchronized 를 사용하면 확실하게 Singleton 패턴으로 만들 수 있다.