EEALL@ONCE

🔦 그래서 싱글턴이 뭔데? Singleton 본문

분류없음지식🔦

🔦 그래서 싱글턴이 뭔데? Singleton

올엣원스 2023. 12. 13. 00:42
728x90

싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근​(액세스) 지점을 제공하는 생성 디자인 패턴입니다.

 

이 이미지는 소프트웨어 개발에서 싱글턴 디자인 패턴의 개념을 설명하기 위해 만들어졌습니다. 중앙에 '싱글턴' 객체가 있으며, 이는 그 고유함을 상징합니다. 주변에는 싱글턴에 접근하려는 여러 작은 객체들이 있으며, 이들은 모두 같은 싱글턴 인스턴스로 리디렉션됩니다. 이를 통해 시스템 내에서 싱글턴 인스턴스가 하나만 존재한다는 점이 강조됩니다. 화살표는 다른 객체들로부터 싱글턴으로의 접근 방향을 나타냅니다.

 

이 그림은 소프트웨어 개발에서 싱글턴 디자인 패턴이 가질 수 있는 잠재적 문제점들을 묘사하고 있습니다. 중앙에 큰 '싱글턴' 객체가 있으며, 주변에는 다음과 같은 문제들이 강조되어 있습니다:

  1. 유연성 부족: 싱글턴에 연결된 강고하고 융통성 없는 구조물들로 표현되어 있습니다. 이는 싱글턴 패턴이 변경에 어려움을 겪을 수 있음을 나타냅니다.
  2. 테스팅 어려움: 오류 메시지가 있는 컴퓨터 앞에서 좌절한 프로그래머의 모습을 통해 테스트의 어려움을 나타냅니다. 싱글턴은 테스트 환경에서 조작이 어려울 수 있습니다.
  3. 긴밀한 결합: 싱글턴과 다른 객체들을 연결하는 긴밀히 얽힌 로프로 표현되어 있습니다. 이는 시스템의 다른 부분들과 싱글턴 사이에 긴밀한 의존성이 생길 수 있음을 의미합니다.
  4. 확장성 저해: 싱글턴에 접근하려는 객체들의 교통 체증으로 확장성 문제를 시각화하고 있습니다. 많은 객체들이 싱글턴에 접근하려 할 때 발생할 수 있는 병목 현상을 나타냅니다.

이러한 요소들은 싱글턴 패턴의 단점들을 분명하게 보여주고 있습니다.

 


package singleton;

public class Setting {
    private int volume =5;
    public int getVolume(){
        return volume;
    }
    public void incVolume(){volume++;}
    public void decVolume(){volume--;}
}
package singleton;

public class Tab {
    private Setting setting = new Setting();

    public Setting getSetting(){
        return setting;
    }
}
package singleton;

public class Main {
    public static void main(String[] args) {
        Tab tab1 = new Tab();
        Tab tab2 = new Tab();
        Tab tab3 = new Tab();

        System.out.println(tab1.getSetting().getVolume());

        System.out.println("\n- - - - -\n");

        tab1.getSetting().incVolume();
        tab1.getSetting().incVolume();

        System.out.println(tab1.getSetting().getVolume());

        //  ⚠️ 각 인스턴스는 서로 다른 Setting 인스턴스를 가짐
        //  - 설정값이 공유되지 못함
        System.out.println(tab2.getSetting().getVolume());
        System.out.println(tab3.getSetting().getVolume());

    }

}

이 코드는 Java로 작성된 간단한 예제로, singleton 패키지 내에 세 개의 클래스(Main, Tab, Setting)가 정의되어 있습니다. 이 예제는 싱글턴 패턴의 필요성을 보여주기 위해 일반적인 객체 생성 방식을 사용합니다.

클래스 설명

  1. Main 클래스:
    • 이 클래스는 프로그램의 진입점인 main 메서드를 포함합니다.
    • Tab 객체 세 개 (tab1, tab2, tab3)를 생성합니다.
    • 첫 번째 Tab (tab1)의 Setting을 조작하여 볼륨을 증가시킵니다.
    • 각 Tab의 볼륨을 출력하여 Setting 객체가 각 Tab 인스턴스마다 다르게 존재함을 보여줍니다.
  2. Tab 클래스:
    • Setting 객체의 인스턴스를 가지는 클래스입니다.
    • getSetting() 메서드를 통해 해당 Tab의 Setting 인스턴스에 접근할 수 있습니다.
  3. Setting 클래스:
    • 볼륨을 관리하는 클래스입니다.
    • 볼륨은 기본값으로 5로 설정되어 있습니다.
    • getVolume(), incVolume(), decVolume() 메서드를 통해 볼륨을 조회, 증가, 감소시킬 수 있습니다.

코드의 문제점

  • 이 코드는 Setting 인스턴스를 각 Tab 객체마다 별도로 생성합니다. 이는 Setting이 공유되어야 하는 상황에서 문제가 될 수 있습니다.
  • 예를 들어, 모든 Tab 객체가 동일한 볼륨 설정을 공유해야 한다면 현재 구현에서는 이를 달성할 수 없습니다. tab1의 볼륨을 변경해도 tab2와 tab3의 볼륨은 변경되지 않습니다.

싱글턴 패턴의 적용

  • 이 문제를 해결하기 위해 Setting 클래스를 싱글턴 패턴으로 구현할 수 있습니다.
  • 싱글턴 패턴을 사용하면 모든 Tab 객체가 동일한 Setting 인스턴스를 공유하게 되어, 어느 하나의 Tab에서 볼륨을 변경하면 모든 Tab에 반영됩니다.
  • 이를 구현하기 위해서는 Setting 클래스 내에 싱글턴 로직을 추가하고, 각 Tab 객체가 이 싱글턴 인스턴스에 접근하도록 변경해야 합니다.

package singleton;

public class Tab {
    private Setting setting = null;

    public Tab(){

    }

    public Tab(Setting setting) {
        this.setting = setting;
    }

    public void setSetting(Setting setting) {
        this.setting = setting;
    }

    public Setting getSetting(){
        return setting;
    }
}
package singleton;

public class Main {
    public static void main(String[] args) {
        //  공유시킬 인스턴스
        Setting setting = new Setting();

        //  💡 방법 1 : 생성자로 주입
        Tab tab1 = new Tab(setting);
        Tab tab2 = new Tab(setting);

        //  💡 방법 2 : setter로 주입
        Tab tab3 = new Tab();
        tab3.setSetting(setting);

        System.out.println(tab1.getSetting().getVolume());
        System.out.println(tab2.getSetting().getVolume());
        System.out.println(tab3.getSetting().getVolume());

        System.out.println("\n- - - - -\n");

        tab1.getSetting().incVolume();
        tab1.getSetting().incVolume();

        System.out.println(tab1.getSetting().getVolume());
        System.out.println(tab2.getSetting().getVolume());
        System.out.println(tab3.getSetting().getVolume());

        //  🤔 인스턴스를 공유할 수 있게 되었지만 번거로움이 남음
        //  - 해당 인스턴스를 외부에서 주입해주어야 함
        //    - 협업 등의 경우 잘못 사용될 여지가 있음
        //  - 더 편리하고 안전한 방법은 없을까?

    }

}

이 코드 예제에서는 싱글턴 패턴 대신 다른 방법으로 Setting 인스턴스를 공유하고 있습니다. 이 접근 방식은 싱글턴 패턴의 일부 문제점을 해결하려고 하지만, 여전히 몇 가지 단점이 있습니다.

구현 방식

  • Setting 인스턴스 공유:
    • Setting 클래스의 인스턴스를 하나만 생성하고, 이를 모든 Tab 객체와 공유합니다.
  • 생성자 주입 (Constructor Injection):
    • Tab 객체를 생성할 때, Setting 인스턴스를 생성자를 통해 주입합니다 (tab1, tab2).
  • 세터 주입 (Setter Injection):
    • 빈 Tab 객체를 생성한 후, 세터 메서드를 통해 Setting 인스턴스를 설정합니다 (tab3).

코드의 문제점

  • 외부 의존성:
    • Setting 인스턴스는 외부에서 생성되고 관리되어야 합니다. 이는 코드의 복잡성을 증가시키고, 실수로 인한 잘못된 사용 가능성을 높일 수 있습니다.
  • 편의성 부족:
    • Setting 인스턴스를 모든 Tab 객체에 수동으로 주입해야 합니다. 이는 코드 작성 시 추가적인 노력을 요구하며, 실수를 유발할 수 있습니다.

싱글턴 패턴으로의 개선

싱글턴 패턴을 적용하면 이러한 문제들을 해결할 수 있습니다. 싱글턴 패턴을 사용하면 Setting 인스턴스는 시스템 전체에 걸쳐 하나만 존재하며, 모든 Tab 객체는 자동으로 이 공유된 인스턴스에 접근할 수 있습니다. 이는 코드를 간결하게 만들고, 실수의 가능성을 줄여줍니다.

다만, 싱글턴 패턴은 전역 상태를 만들고, 테스트가 어려워지며, 코드의 결합도를 높이는 등의 단점도 가지고 있습니다. 따라서 싱글턴 패턴을 적용할 때는 이러한 장단점을 고려해야 합니다.

 


package singleton;

public class Setting {
    //  ⭐️ 이 클래스를 싱글턴으로 만들기

    // 클래스(정적) 필드
    // - 프로그램에서 메모리에 하나만 존재
    private static Setting setting;

    //  ⭐️ 생성자를 private으로!
    // - 외부에서 생성자로 생성하지 못하도록
    private Setting () {}

    //  💡 공유되는 인스턴스를 받아가는 public 클래스 메소드
    public static Setting getInstance() {
        //  ⭐️ 아직 인스턴스가 만들어지지 않았다면 생성
        //  - 프로그램에서 처음 호출시 실행됨
        if (setting == null) {
            setting = new Setting();
        }
        return setting;
    }

    private int volume = 5;

    public int getVolume() {
        return volume;
    }
    public void incVolume() { volume++; }
    public void decVolume() { volume--; }
}
package singleton;

public class Tab {
    //  ⭐️ 공유되는 유일한 인스턴스를 받아옴
    private Setting setting = Setting.getInstance();

    public Setting getSetting() {
        return setting;
    }
}
package singleton;

public class Main {
    public static void main(String[] args) {
        Tab tab1 = new Tab();
        Tab tab2 = new Tab();
        Tab tab3 = new Tab();

        System.out.println(tab1.getSetting().getVolume());
        System.out.println(tab2.getSetting().getVolume());
        System.out.println(tab3.getSetting().getVolume());

        System.out.println("\n- - - - -\n");

        tab1.getSetting().incVolume();
        tab1.getSetting().incVolume();

        System.out.println(tab1.getSetting().getVolume());
        System.out.println(tab2.getSetting().getVolume());
        System.out.println(tab3.getSetting().getVolume());

        //  🎉 외부에서 각 사용처들을 신경쓸 필요 없음

    }

}

이 코드는 싱글턴 디자인 패턴을 적용하여 Setting 클래스를 구현한 예제입니다. 싱글턴 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하며, 이 인스턴스에 대한 전역 접근점을 제공합니다.

Setting 클래스

  • 정적 필드:
    • private static Setting setting;: Setting 클래스의 유일한 인스턴스를 저장하는 정적 필드입니다.
  • 생성자:
    • private Setting() {}: 생성자를 private으로 선언하여, Setting 클래스 외부에서 인스턴스를 직접 생성할 수 없도록 합니다.
  • getInstance 메서드:
    • public static Setting getInstance(): 이 정적 메서드는 Setting의 유일한 인스턴스를 반환합니다.
    • 인스턴스가 아직 생성되지 않았다면, 새로운 인스턴스를 생성하여 setting 필드에 할당합니다.
    • 이후 이 메서드가 호출될 때마다 같은 인스턴스를 반환하여, 전체 프로그램에서 하나의 Setting 인스턴스만 사용되도록 합니다.
  • 볼륨 관련 메서드:
    • 볼륨을 관리하는 메서드들(getVolume, incVolume, decVolume)이 포함되어 있습니다.

Tab 클래스

  • 인스턴스 필드:
    • private Setting setting = Setting.getInstance();: Setting 클래스의 유일한 인스턴스를 가져와서 저장합니다.
  • getSetting 메서드:
    • public Setting getSetting(): 이 메서드를 통해 Tab 객체가 갖고 있는 Setting 인스턴스에 접근할 수 있습니다.

Main 클래스

  • Main 클래스의 main 메서드에서는 세 개의 Tab 객체를 생성하고, 각각의 Setting 인스턴스의 볼륨을 출력합니다.
  • tab1의 Setting 인스턴스를 통해 볼륨을 증가시킨 후, 모든 Tab 객체의 Setting 인스턴스의 볼륨을 다시 출력합니다.
  • 싱글턴 패턴을 적용했기 때문에, 모든 Tab 객체가 같은 Setting 인스턴스를 공유합니다. 따라서 tab1에서 볼륨을 변경하면 tab2와 tab3에서도 변경된 볼륨이 반영됩니다.

이 코드는 싱글턴 패턴을 사용하여 인스턴스의 중복 생성을 방지하고, 전역에서 접근 가능한 단일 인스턴스를 제공합니다. 이 방식은 설정 정보와 같이 공유되어야 하는 리소스 관리에 적합합니다.


  1. 첫 번째 방법 - 독립적 인스턴스 생성:
    • 이 방식에서는 각 Tab 객체가 자신의 Setting 인스턴스를 독립적으로 생성합니다.
    • 그림에서, 여러 스틱 피규어(각각 'Tab'으로 라벨링)가 각기 다른 박스('Setting'으로 라벨링)를 들고 있는 모습을 볼 수 있습니다.
    • 이는 각 Tab이 고유한 Setting 인스턴스를 가지고 있음을 나타냅니다.
  2. 두 번째 방법 - 공유 인스턴스 전달:
    • 이 경우 하나의 Setting 인스턴스를 여러 Tab 객체가 공유합니다.
    • 중앙에 큰 박스('Setting'으로 라벨링)가 있으며, 주변에 여러 스틱 피규어('Tab'으로 라벨링)가 선으로 연결되어 있습니다.
    • 이는 모든 Tab 객체가 동일한 Setting 인스턴스를 공유한다는 것을 의미합니다.
  3. 세 번째 방법 - 싱글턴 패턴:
    • 싱글턴 패턴에서는 Setting 클래스의 단 하나의 인스턴스만이 전체 시스템에서 생성되고 사용됩니다.
    • 그림에서, 중앙의 큰 박스('Setting'으로 라벨링)와 주변의 여러 스틱 피규어('Tab'으로 라벨링)가 모두 같은 'Setting' 박스에 연결되어 있습니다.
    • 이는 모든 Tab 객체가 동일한 싱글턴 Setting 인스턴스에 접근하고 있음을 나타냅니다.

 



그래서 내가 이해한 싱글턴이란..

클래스에 인스턴스가 하나만 있도록 만들어서 모두가 공유하는 것 

그래서 한번만 업데이트를 하면 모두가 적용되는 뭐 그런 것 것 같다.. 

728x90