본문 바로가기

Software Development/디자인 패턴

[디자인 패턴의 정석] 생성패턴 - 싱글턴 패턴 (Singleton)

 

개발자 yeah Developer Story - 디자인 패턴

 

오늘은 저번 포스팅에 이어서 싱글턴 패턴 을 이야기해보려 합니다.

 

 

 

먼저 싱글턴 패턴을 설명하기 앞서 객체 생성에 대해 알아보도록 하겠습니다.

 

선언된 클래스객체로 생성하는 과정인스턴스화 라고 합니다.

 

 

객체 생성의 원리

 

 

인스턴스화선언된 클래스를 기반으로 객체를 생성해 메모리에 할당하는 작업을 수행하는데요.

 

이 모든일을 수행하게 하는 키워드가 바로 new 키워드 입니다!

 

 

이러한 객체 생성 과정은 new 키워드를 통해 반복 생성할 수 있습니다.

 

한번 선언된 클래스를 이용하여 동일한 객체를 제한없이 무제한으로 생성 가능합니다. (시스템 자원이 허락하는 한에서)

 

 

클래스를 이용하여 객체를 생성

 

이러한 이유로 객체를 붕어빵과 붕어빵틀(?) 로 비유하여 설명하는 경우가 많은 것이죠.

 

이는 객체지향의 원리이며 특징입니다.

 

하지만 이러한 특징은 장점이면서 때로 단점이 되기도 합니다.

 


객체 지향에서 new 키워드로 생성한 객체는 각각 독립된 자원입니다.

 

독립된 자원이란 서로 다른 메모리 영역을 차지하고 있다는 뜻인 거죠.

 

만약 하나의 객체를 생성하여 이를 공유하려는 코드를 작성 시, 복수의 객체를 생성하는 new키워드는 문제를 야기할 수 있습니다.

 

 

예를 들어 A클래스와 B클래스에서 DataBase 관련 클래스를 인스턴화 해서 사용한다면?

 

동일한 DataBase 클래스를 공유하고 싶었지만 각각 A클래스B클래스에서 생성된 DataBase 객체는 다릅니다.

 

물론 A클래스에서 생성된 DataBase 객체B클래스로 전달하면 되지만...

 

new키워드로 언제든지 DataBase 객체가 생성 가능하다는 것은 공유 목적에 부합하지 않는 잠재적인 문제점을 안고 가는 것입니다.

 

 

이를 해결하기 위한 적합한 패턴이 바로 싱글턴 패턴입니다!!

 


싱글턴 패턴은 다른 생성 패턴과 달리 하나의 객체만 생성을 제한하는 패턴입니다.

 

따라서 생성된 객체는 공유되어 어디서든 접근 할 수 있습니다.

 

 

먼저 객체를 공유하려면 객체를 복수로 생성하는 문제를 해결해야 합니다.

 

이를 해결하려면 객체 생성을 제한해야 하는데요!

 

제한이란 의미는 new키워드를 사용할 때 서로 다른 A, B 객체가 생성되는 것이 아니라 동일한 객체 1개만 유지된다는 의미입니다.

 

 

어떻게 동일한 객체 1개만 유지하는지 코드로 살펴보도록 하겠습니다.

 

public class DataBase {
    private int mUserCnt = 0;   //DB를 사용하는 User의 수.

    private DataBase() { };     //생성자를 통해 객체생성을 막기위해 private 처리.

    void setUserCnt(int cnt) { mUserCnt = cnt; }    //DB를 사용하는 User의 수를 set.
    int getUserCnt() { return mUserCnt; }           //DB를 사용하는 User의 수를 반환.
}

 

먼저 클래스의 생성자 접근 제한으로 인해 new 키워드객체 생성 동작을 막아야 합니다. 

 

생성자private으로 선언하면 외부에서 선언을 막을 수 있습니다.

 

 

 

public class DataBase {
    private static DataBase dbInstance = null;      //중복객체 생성을 막기위한 참조체

    private int mUserCnt = 0;   //DB를 사용하는 User의 수.

    private DataBase() { };     //생성자를 통해 객체생성을 막기위해 private 처리.

    /* 내부적으로 중복생성을 방지하는 로직 */
    public synchronized static DataBase getInstance() {
        if (dbInstance == null) {		//이미 객체가 생성되었는지 판단
            dbInstance = new DataBase();
        }
        return dbInstance;
    }

    void setUserCnt(int cnt) { mUserCnt = cnt; }    //DB를 사용하는 User의 수를 set.
    int getUserCnt() { return mUserCnt; }           //DB를 사용하는 User의 수를 반환.
}

 

다음은 하나의 객체만 가질 수 있도록 내부 참조체를 선언하고 내부적으로 중복 생성을 방지하는 로직을 추가합니다.

 

싱글턴 패턴 내부적으로 하나의 객체만 보장하기 위해 자체 객체를 저장하는 참조체 를 가져야 합니다.

 

그리고 이 참조체 (DataBase dbInstacne) 를 통해 자신의 객체가 생성되었는지 판단을 할 수 있습니다.

 

※멀티 스레드 환경에서는 해당 참조체에 접근 시 값이 불일치하는 문제가 발생할 수 있는데요. 이때 참조체에 volatile 키워드를 붙이면 MainMemory에 값을 저장하기 때문에 불일치를 방지할 수 있습니다.

 

 

 

또한 싱글턴 패턴에서는 내부적으로 객체를 생성할 수 있도록 특수한 메서드 getInstance() 를 추가하는데요.

 

getInstance()자기 자신의 클래스를 객체로 생성하여 반환합니다.

 

이때 참조체를 확인하여 이미 객체가 생성되었는지 확인을 하여 객체를 생성해 줍니다. (이로써 중복 객체 생성을 방지)

 

 

또한 getInstance()정적 메서드로 구현을 해야하는데요.

 

그 이유는 싱글턴 클래스는 아직 객체가 존재하지않아 객체를 통한 메서드 접근은 할 수 없기 때문입니다.

 

따라서 정적타입으로 메서드를 선언하면 객체 없이도 메서드를 호출 할 수 있습니다.

 

※참조체를 이용하여 중복생성을 방지하는 방법을 플라이웨이트 패턴이라고 합니다. 즉 싱글턴 패턴에는 플라이웨이트 패턴이 활용되고 있는것이죠.

 


다음은 main()을 확인해보도록 하겠습니다.

 

public class SingletonPattern {

    public static void main(String[] args) {
        DataBase firstDB = DataBase.getInstance();
        firstDB.setUserCnt(10);
        System.out.println("현재 firstDB 의 유저 수 : " + firstDB.getUserCnt());

        DataBase secondDB = DataBase.getInstance();
        System.out.println("현재 secondDB 의 유저 수 : " + secondDB.getUserCnt());
    }
}

 

main의 결과

 

main()에서 DataBase의 객체생성을 DataBase의 정적 메서드 getInstance() 를 통해 생성하였습니다.

 

결과를 통해 firstDB 클래스secondDB 클래스 두개의 객체 생성이 동일한 객체를 공유 받고있음을 확인 할 수 있습니다.

 


싱글톤 패턴과 유사한 방법으로 정적 클래스가 존재 하는데요.

 

/* 정적 클래스로 구현한 DataBase */
public class DataBase {
    public static int mUserCnt = 0; //DB를 사용하는 User의 수.

    public static void setUserCnt(int cnt) { mUserCnt = cnt; }    //DB를 사용하는 User의 수를 set.
    public static int getUserCnt() { return mUserCnt; }           //DB를 사용하는 User의 수를 반환.
}

 

정적 클래스는 객체 생성없이 클래스 선언만을 통해 프로그램을 실행 할 수 있습니다. (내부가 static변수 / static 함수로만 이루어짐)

 

일반적인 클래스할당 된 메모리의 객체를 통해 호출되지만,

 

정적 클래스는 객체를 생성하지 않고 소스 코드의 클래스 선언 자체를 객체로 인식하여 접근합니다.

 

그렇기 때문에 정적 클래스는 여러개의 객체로 생성 할 수 없으며, 존재하는 객체도 1개입니다.

 

따라서 싱글톤 패턴과 동일한 효과를 볼 수 있는것이죠.

 


싱글톤 패턴  VS  정적 클래스

 

정적 클래스 는 객체를 메모리에 생성하지 않기 때문에 메모리 관리 차원에서는 효율적입니다.

 

하지만 다형성을 위한 인터페이스를 사용 할 수 없어 모든 동작 기능을 정적클래스 안에서 정의해야 하며,

 

다른 클래스와 관계를 맺거나 클래스 초기화 동작이 복잡할 경우 처리하기 힘들어집니다.

 

하지만 반대로 싱글톤 패턴 정적클래스의 단점을 보완하지만 메모리 관점에서는 효율이 떨어 질 수 밖에 없습니다.

 

따라서 본인이 진행하고 있는 프로젝트 설계에 맞게 적절히 사용하는것이 좋습니다.

 

 

 

 

이렇게 오늘은 싱글톤 패턴 정적클래스  이야기 해봤는데요.

 

궁금하시거나 질문이 있으시면 댓글로 달아주세요 !!

 

감사합니다!