CS

[디자인 패턴] 싱글톤 패턴(Singleton Pattern) 이란?

건우(gunoo) 2023. 6. 20. 17:49
목차)
1. 개요

2. 특징 (장점)
    1) 하나의 인스턴스
    2) 전역적인 접근 및 상태 공유
    3) 지연 초기화
    4) 의존성 관리

3. 적용 시 주의사항 (단점)
    1) 전역상태 관리
    2) 결합도 증가
    3) 메모리 누수(memory leak)

4. Singleton 인스턴스 생성하기
    1) Swift
    2) Javascript

1. 개요

소프트웨어 디자인 패턴 중 하나로, 어떤 class의 인스턴스가 오직 하나만 생성되고 이에 대한 전역적인 접근이 가능하도록 만드는 패턴이다.

 

전역상태를 공유하거나 인스턴스가 여러개 생성되는 것을 방지하기 위해 사용된다.

 

디자인패턴은 목적에 따라 크게 생성, 행동, 구조로 나누어진다.

싱글톤 패턴은 '인스턴스 생성'이 목적이므로 '생성' 패턴으로 분류한다.

 

디자인패턴을 목적에 맞게 이해하는 것은 설계에 대한 방향성과 각 디자인패턴의 장단점을 이해하며 결정 할 수 있도록 도와준다.

2. 특징 (장점)

특징을 살펴보기 전 객체에 대해 간단하게 이해하자.

객체란 class 를 사용하여 생성된 인스턴스 이다.

 

1. 하나의 인스턴스

클래스의 인스턴스가 하나만 생성되도록 보장한다.

2. 전역적인 접근 및 상태 공유

싱글톤 인스턴스는 어디서든 전역적으로 접근할 수 있다.
여러 객체가 동일한 싱글톤 인스턴스에 접근하여 데이터를 공유하거나 상태를 유지할 수 있다.

3. 지연 초기화

싱글톤 인스턴스는 처음 사용되기 전까진 생성되지 않는다.
필요한 시점에 인스턴스를 생성하고 초기화한다.

4. 의존성 관리

의존성 관리를 단순화할 수 있다.
여러 객체가 동일한 싱글톤 인스턴스를 사용하면 객체들은 직접적으로 서로 의존하지 않아도 된다.

예를 들어 여러 객체각 하나의 API와 통신해야 한다고 가정해보자.
싱글톤 패턴으로 API를 구현하면, 여러 객체가 동일한 API 인스턴스와 연결하여 사용할 수 있다.

이는 객체가 API연결을 위해 별도의 의존성을 가지지 않아도 된다는 것을 보여준다.

 

의존성은 어려운 개념이므로 아래 코드를 통해 좀 더 정리해보려고 한다.

 

//Swift
class User{
    let userName:String

        //User는 UserAPI 를 주입받아야 사용할 수 있다.
        init(UserAPI:UserAPIProtocol){
            self.userName = UserAPI().getUser().name
        }
}

 

위의 코드를 보면 User 라는 class를 객체로 만들기 위해서, UserAPI 를 주입받아야 한다.

 

이를 User 는 UserAPI에 의존한다 라고 말한다.

 

UserAPI 가 싱글톤 패턴으로 구현되어 있다면 어떻게 될까?

 

//Swift

class User{
	let userName:String
    
    //주입 받지 않고 싱글톤 패턴으로 구현된 UserAPI를 가져와 사용하고 있다.
    init(){
    	self.userName = UserAPI.shared.getUser().userName
    }

}

 

User를 객체로 만들기 위해 어떠한 것도 주입하지 않아도 된다.

 

이를 별도의 의존성을 거치지 않고 생성할 수 있다고 한다.

3. 적용 시 주의사항 (단점)

모든 것은 적절하게 또는 상황에 맞게 사용해야 한다는 것을 기억하자

1. 전역 상태 관리

전역적인 접근이 가능하다는 것은 장점이자 단점이 될 수 있다.
쉽게 접근이 가능하다고 너무 많은 싱글톤 인스턴스를 생성하게 되면, 의도치 않은 곳에서 접근하여 버그를 일으킬 수 있는 것이다.
이는 디버깅도 어려워 지고 코드의 복잡성을 증가시킬 수 있다.

2. 결합도 증가

싱글톤 인스턴스에 직접 의존하면 class 의 의존성을 낮아 질 수 있지만, 결합도는 증가한다.
이는 코드의 유연성과 확장성을 저하시키고 코드의 재사용성도 낮춘다.

TIP) 의존성도 낮추고 결합도도 낮추려면 어떻게 해야 할까?

객체지향의 설계 원칙 중 하나인 의존성 역전 원칙 에 따라 DI(Dependency Injection)패턴을 적용하면 된다.

3. 메모리 누수(memory leak)

메모리 누수(memory leak)란 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상이다.

싱글톤 인스턴스의 특징으로 단 하나의 인스턴스만 존재하고 애플리케이션 어느 곳에서나 접근 가능하며, 모든 코드에서 동일한 인스턴스를 참조 할 수 있다.

이 뜻을 메모리 관점에서 해석하면, 인스턴스가 생성되면 애플리케이션이 실행되는 동안 계속 메모리를 차지하고 유지한다는 뜻이다.

만일 생성이후 잘 사용하지 않는다면 의미 없는 메모리를 계속 차지하고 있는 것이며, 이를 메모리 누수가 발생했다고 한다.

4.  싱글톤 생성하기

1. Swift

import Foundation

class MySingleton {
    static let shared = MySingleton()
    var userName:String = ""
    
    private init() {
        
    }
    
    func updateUser(name:String) {
        self.userName = name
    }
}

MySingleton.shared.updateUser(name: "name")

print(MySingleton.shared.userName)//출력: name

 

[코드 설명]

 

1. static let keyword

static let keyword를 활용하여 정적 상수 shared를 단일 인스턴스로 저장한다.

 

2. private init()

싱글톤 생성시에는 외부에서의 인스턴스화를 방지해야 하므로 private으로 선언하여,
필요한 초기화(init)를 한다.

 

3. 인스턴스 접근

static let 으로 선언된 상수를 통해 인스턴스로 접근하고, 프로퍼티 또는 메서드를 호출한다.

ex) MySingleton.shared.userName

2. Javascript

class Singleton {
  name = "";
  private static instance: Singleton;

  private constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
    }
    return Singleton.instance;
  }

  // 메서드 정의
  updateUserName(name: string) {
    this.name = name;
  }
}


// 사용 예시
const singletonInstance1 = new Singleton();
const singletonInstance2 = new Singleton();

singletonInstance1.updateUserName("name")

console.log(singletonInstance1.name)
console.log(singletonInstance2.name)

 

[코드 설명]

 

1. private static instance: Singleton

Singleton 이라는 Class 의 단일 인스턴스를 저장하기 위한 프로퍼티이다.

생성시( constructor(){...} )에 인스턴스가 저장되어 있는지 유무를 체크하고 없을 시 현재 객체(this)를 할당한다.

 

2. singletonInstance1 과 singletonInstance2 는 동일한 인스턴스를 참조

사용 예시 코드를 보면, singletonInstance1 과 singletonInstance2 는 각각 인스턴스를 생성하는 것으로 보인다.

그러나 생성시 싱글톤 패턴으로 구현되어 있으므로 단일 인스턴스가 되고,
singletonInstance1에서 upateUserName("name") 메서드를 실행시켰는데, 
singletonInstance1 과 singletonInstance2 에서 같은 이름으로 업데이트 되는 것을 확인 할 수 있다.