[Structure] 싱글톤 패턴과 문제점

Song-YunMin·2021년 5월 15일
5

싱글톤이란?

  • 싱글톤은 전역 변수를 사용하지 않고 객체를 하나만 생성 하도록 함
  • 생성된 객체를 어디에서든지 참조할 수 있도록 하는 패턴
  • 생성자가 여러 차례 호출되더라도 실제로 생성되는 객체는 하나
  • 최초 생성 이후에 호출된 생성자는 최초에 생성자가 생성한 객체를 리턴한다.
  • 환경설정 관리 클래스나 커넥션 풀과 같이 pool 형태로 관리되는 공통 클래스에 사용되는 것이 일반적

싱글톤의 특징

  • 고정된 메모리 영역을 얻으면서 동시에 단 한번만 new 를 사용하여 메모리 낭비를 방지할 수 있다.
  • 싱글톤으로 만들어진 인스턴스는 전역으로 사용됨. 즉, 다른 클래스의 인스턴스 들이 데이터를 공유하고 변경할 수 있다.
  • 너무 많은 일을 위임하거나 공유할 경우 Coupling이 많아지고 결합도가 높아진다.

싱글톤 기본 예제

JAVA

Javascript

Javascript 에서의 Singleton Pattern 간단예제는 위와같이 구현할 수 있습니다.

멤버 메소드들을 init() 메서드에 정의하고, getInstance 메서드를 통해 instance 생성 시 최초 생성인지 아닌지를 판단합니다. 최초 생성이라면 멤버 메서드를 instance에 삽입하고, 아니라면 기존에 삽입했던 instance를 삽입합니다.

firstsecond 에 Singleton의 instance를 생성하고 first instance 만 변경을 시도해본 후, 출력해보면 second instance의 값도 변경이 된 것을 알 수 있습니다.

static 키워드를 사용한 Singleton

위 예제의 TestClass1 은 Singleton을 적용하지 않은 클래스이고 TestClass2 는 Singleton 을 적용한 메서드입니다.

Singleton 을 적용한 TestClass2 를 살펴보면, static 이라는 키워드를 사용하여 instance 라는 변수를 메모리의 Data 영역에 저장함, 메모리의 Data 영역은 프로그램이 종료될 때 까지 메모리에 남아있기 때문에 메모리공간을 공유할 수 있음. 즉, 메모리 공간을 공유할 수 있습니다.

constructor 를 보면, 위 예제와 비슷하다. instance가 정의되어 있다면 정의되었던 instance를 반환하고. 그게 아니라면 this 를 instance에 대입합니다.

즉, 최초 생성시엔 Class를 자체를 instance에 할당하고, 두번째 부턴 첫번째 할당한 instance를 반환하는 것입니다.

싱클톤 패턴이 필요한 이유

예를들어 사무실에 있는 1대의 프린터를 여러명이 사용하는 경우를 예시로 들어보겠습니다.

이러한 상황에서 가장 정상적인 프린터 사용법은, 1대만 존재하는 프린터를 여러 사람이 함께 공유하며 사용하는 방법이 좋습니다.

위 사진에서 오른쪽의 경우, 프린터를 사용하려는 사람들이 프린터를 각자 생성해서 사용하는것은 불가능합니다. 프린터는 단 1대만 존재하기 때문입니다.

조금 더 실전에 가까운 예는, 사용자 정보를 처음에만 로딩해주는 UserManager 가 있다고 가정한다면, 매번 이 인스턴스를 생성하는 것은 자원 낭비 또는 인스턴스가 꼬이는 일이 생길 수 있습니다.

또한 DB Connection Pool같은 객체가 있을때 한번만 연결한 후 한번만 끊는 것이 더 효율적이라고 할 수 있겠습니다.

싱글톤 패턴에 주의할 점

Singleton Pattern 사용시 주의해야 할 점은, 상태를 가진 객체를 Singleton 으로 만들면 안된다는 것입니다. 앱 내의 단 한개의 인스턴스가 존재하고, 이를 전역에서 접근할 수 있다면 각기 다른 스레드에서 객체의 상태를 마구잡이로 변경시킬 여지가 있습니다. 상태가 공유된다는 것은 매우 위험한 일이기 때문에 무상태 객체 혹은 설계상 유일해야하는 시스템 컴포넌트를 Singleton으로 만들어야 합니다.

싱글톤이 안티패턴이라고 불리는 이유

SOLID 원칙의 대부분은 인터페이스 설계와 관련이 되어 있습니다. 의존성을 Interface에 두면 실제 구현 클래스의 구현이 변경되어도 이를 사용한 코드는 큰 영향을 받지 않습니다. 그렇기 때문에 SOLID원칙을 지키기 위해서는 인터페이스로 설계하는게 좋은 설계입니다.

하지만 Singleton을 사용하는 경우 대부분 인터페이스가 아닌 구현 클래스의 객체를 미리 생성해놓고 정적 메소드를 이용하여 구현하게 됩니다. 이는 SOLID 원칙을 위반할 수 있는 가능성이 있으며, 동시에 Singleton을 사용하는 곳과 Singleton Class 사이에 의존성이 생기게 됩니다.

이는 결합도를 높이는 행위로, 수정 및 단위테스트의 어려움이 생기게 됩니다.

객체지향의 의도와 맞지 않는다.

Singleton의 사용은 Global state를 만들 수 있기 때문에 바람직한 방법은 아닙니다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 지양되어야 할 모델입니다. 또한 Singleton은 Java의 경우 Private 생성자를 가지고 있기 때문에 상속할 수 없습니다. 이는 다형성같은 객체지향의 특징을 적용할 수 없게 됩니다.

리팩토링에서의 영향도는 프로그램 전체가 된다.


Service A 를 위해 만들어졌던 Func 1 을 다른 서비스들이 사용하기 시작합니다. Service A 의 요구사항이 변경되어 Func 1 이 변경되면 이를 사용하는 모든 서비스들에게 변경 전파(Shotgun Surgery)가 이루어집니다. 그리고 이러한 Coupling 문제를 개선하기 위해 Singleton을 리팩토링 할 때의 영향도(Force)는 어플리케이션 전체가 됩니다.

profile
고독한 서버 개발 3년차

1개의 댓글

comment-user-thumbnail
2023년 1월 4일

꼼꼼한 설명감사합니다 ! 도움이 많이 되었습니다.

답글 달기