본문 바로가기

Go

golang 싱글톤 패턴(Singleton Pattern with golang)

목적

싱글톤 패턴은 클래스가 하나의 인스턴스만 갖도록 하는 동시에 이 인스턴스에 대한 전역 접근 지점을 제공한다

문제

싱글톤 패턴은 단일 책임 원칙을 위반하여 다음 2가지 문제를 해결한다

  1. 클래스에 인스턴스가 하나만 있는지 확인한다 클래스에 있는 인스턴스 수를 제어하려는 사람이 있는 일반적인 이유는 일부 공유 리소스(예: 데이타베이스 또는 파일)에 대한 엑세스를 제어하기 위한 것이다. 작동 방식은 객체를 생성한 이후 새로운 객체를 재생성했을 때, 새로운 객체를 반환받지 않고 이미 생성된 객체를 반환받는 것과 같다. 생성자 호출은 반드시 항상 새 객체를 의도적으로 반환해야 하므로 이 동작은 일반 생성자로 구현할 수 없다
  2. 해당 인스턴스에 대한 전역 접근 지점을 제공한다 일부 필수 객체를 저장할 때 사용했던 전역 변수는 매우 편리하지만 잠재적으로 해당 변수의 내용을 덮어쓰거나 충돌할 위험이 있으므로 안전하지 않다. 싱글톤 패턴은 전역 변수와 마찬가지로 프로그램 어디에서나 일부 객체에 접근할 수 있게 하지만, 다른 코드가 덮어쓰지 않도록 보호한다. 이 문제의 다른 측면은 문제 #1을 해결한 코드가 프로그램 전체에 흩어져 있지를 원하지 않으며, 하나의 클래스에 담겨 있기를 원한다는 것이다.

해결

싱글톤의 모든 구현에는 다음 두 단계의 공통점이 있다

  • 기본 생성자를 private으로 설정하여, 싱글톤 클래스 new 연산자로 호출되지 못하도록 한다
  • 생성자 역할을 하는 static 생성 메소드를 만든다. 내부적으로 이 메소드는 private 생성자를 호출하여 객체를 만들고 static 필드에 저장한다. 이후 이 메소드를 호출하면 캐싱된 객체를 반환한다.

실세계 유추

정부는 싱글톤 패턴의 훌륭한 예시가 될 수 있다. 국가는 하나의 정부만 가지며, ‘X의 정부'라는 제목은 정부를 구성하는 개인의 신원에 관계없이 해당 그룹을 식별하는 전역 접근 지점이 된다.



까지가 https://refactoring.guru/design-patterns/singleton

 

Singleton

Real-World Analogy The government is an excellent example of the Singleton pattern. A country can have only one official government. Regardless of the personal identities of the individuals who form governments, the title, “The Government of X”, is a g

refactoring.guru

에 발췌된 내용이다. 디자인 패턴에 관한 내용은 거의 자바 기준으로 설명하니 참고하면 좋겠다.

구현 방법

보통 싱글톤 패턴을 가지는 인스턴스는 처음에 초기화할때만 생성된다.
예를들어 점수를 합산하는 Queue 시스템을 만들어 보고 싶다고 가정하면 
이 Queue 는 처음에 한번만 초기화 되어야 하고 프로그램이 돌아가는동안 이 Queue 는 다시 초기화되면 안된다.

1. Queue 구조체를 만든다
2. 전역변수를 만들고 이 Queue 를 바라보게 한다.
3. 초기화 하는 함수를 만들어 생성한다.

 

예제 코드

package main

import (
   "fmt"
   "time"
)

var ScoreQueue *Queue

func main() {
   // 싱글톤 패턴을 만족하려면 초기화 함수를 여러번 실행해도
   // 재할당되지 않고 넘어가게 된다.
   initialize()
   initialize()
   initialize()
   initialize()
   for i := 0 ; i < 5 ; i++ {
      go ScoreQueue.Work()
   }

   go func() {
      ScoreQueue.scoreChannel <- 1
      ScoreQueue.scoreChannel <- 2
   }()

   time.Sleep(time.Second*2)
}

type Queue struct {
   scoreChannel   chan int
   workingChannel chan bool
}

func initialize() {
   // 이부분이 싱글턴 패턴을 만족하는 코드
   if ScoreQueue == nil {
      fmt.Println("ScoreQueue 초기화")
      ScoreQueue = newQueue()
   } else {
      fmt.Println("이미 생성됨")
   }
}

func newQueue() *Queue {

   scoreChannel := make(chan int, 100)
   workingChannel := make(chan bool, 100)
   return &Queue{
      scoreChannel:   scoreChannel,
      workingChannel: workingChannel,
   }
}

func (q *Queue) Work() {

   defer func() {
      if r := recover(); r != nil {
         go q.Work()
      }
   }()

   for {
      select {

      case contents := <-q.scoreChannel:
         q.workingChannel <- true
         scoreQueueHandler(contents)
         <-q.workingChannel

      }
   }
}

func (q *Queue) Size() int {
   return len(q.workingChannel)
}

func scoreQueueHandler(contents int) {
      fmt.Println("add" , contents)
}