팩토리 메서드와 싱글톤

타키탸키·2023년 4월 11일
0

GO-WEB

목록 보기
11/11

배경

메시지 브로커를 인터페이스로 정의하면서 Redis 구현체를 생성했다. 이와 관련하여 내가 필요했던 기능은 생성자 주입과 싱글톤 객체였다.

Java와 다르게 Go는 생성자 개념이 없어 New라는 키워드를 붙인 임의의 생성자 함수를 직접 작성해야 한다. 또한, 스프링 프레임워크처럼 빈을 관리하는 컨테이너 개념도 없기 때문에 생성자 주입이나 싱글톤 기능도 직접 구현해야 한다.

설계에 따르면, 메시지 브로커의 구현체는 생성자 주입을 통해 다른 구현체(kafka 등)로 쉽게 변경될 수 있어야 하며, 시스템이 처음 실행될 때 딱 한 번 생성되어야 한다.

구현

  1. 팩토리 메서드를 활용하여 생성자 주입이 가능한 구조를 설계했다
    • Go의 생성자 함수는 일반 함수로 정의한다
    • 따라서, 외부에서 직접적으로 접근하지 못하도록 함수명을 소문자로 정의했다
    • 인터페이스 이름 또한 소문자로 정의해서 직접 접근을 막도록 구현했다
    • 인터페이스와 구현체에 접근하려면 반드시 이 팩토리 메서드를 호출해야 한다
    • 추후에 메시지 브로커가 변경되면, return 부분만 수정하면 된다
func 메시지_브로커_생성하기() 메시지_브로커_인터페이스 {
	return new_메시지_브로커_구현체()
}
  1. Go의 sync.Once 객체를 사용하여 싱글톤을 구현했다
    • 싱글톤 객체를 반환하는 GetInstance 함수를 구현했다
    • once.Do() 함수 내부에서 instance 변수를 초기화하면, 그 다음 getInstance 함수 호출 시에는 once.Do()를 실행하지 않고 초기화된 instance 변수를 반환한다
func GetInstance(ip, port string) 메시지_브로커_인터페이스 {
	var once sync.Once
	var instance 메시지_브로커_인터페이스
	once.Do(func() {
		instance = 메시지_브로커_생성하기()
	})
	return instance
}

3. 팩토리 메서드 내부에 getInstance 정의하기
- 객체를 생성하는 팩토리 메서드에 getInstance를 정의하면 싱글톤으로 정의된 객체가 반환된다

  1. (수정)GetInstance 내부에 팩토리 메서드 정의하기
    • 팩토리 메서드가 생성자 함수를 직접 호출하고 그 결과를 GetInstance로 반환하는 것이 더 자연스럽다고 판단되어 기존 설계를 변경했다
    • 사실상 팩토리 메서드와 GetInstance 모두 객체를 생성한다는 점에서 같은 기능을 한다고 볼 수 있기 때문에 그에 따라 두 함수 중 하나만 가져가도 된다
    • 그러나 엄밀히 따지면, 팩토리 메서드는 의존성 주입을 위한 역할을 수행하고 getInstance는 싱글톤 객체를 생성하는 역할을 수행하므로 두 역할은 분리되어야 한다
    • Go에서는 인터페이스 타입도 메모리 주소를 가지고 있기 때문에 포인터 타입 변수와 동일하게 동작한다
    • 인터페이스 타입 변수에 구현체의 주소를 할당하면 실제 구현체의 값이 저장되어 있는 메모리 주소가 해당 변수에 저장 된다
    • 따라서, 구현체 생성자 함수의 반환 타입이 구현체의 포인터라 하더라도 이를 반환하는 팩토리 메서드의 반환 타입이 인터페이스이므로 호환이 가능하다
    • 이렇게 하면, 구현체의 생성자 함수가 구현체의 주소를 반환할 수 있고 그 주소가 GetInstance에서 선언된 인터페이스 타입의 instance 변수에 할당될 수 있다
func new_메시지_브로커_구현체() 메시지_브로커_구현체_포인터 {
	return &메시지_브로커_구현체
}

논점

그러나, 여전히 팩토리 메서드와 getInstance가 동일한 기능을 하고 있다는 점에서 위와 같은 설계를 가져갈 경우에 불필요한 레이어를 늘어난다는 생각이 든다. 따라서, 팀원과 협의 후에 역할 분리와 최소한의 레이어 중 어떤 게 더 효율적인지를 결정할 예정이다.


참고

일반적으로 go의 생성자 함수는 필드의 값을 초기화하기 위해 포인터를 사용한다. 구조체 타입을 반환할 경우, 생성자 함수를 호출할 때 필드명을 함께 넘겨야 해서 다소 번거롭다. 이때, 필드명을 넘기지 않으면 컴파일 에러가 발생한다.

  • 포인터 타입으로 반환
    p := NewPerson("John", 30)

  • 구조체 타입으로 반환
    p := NewPerson(name: "John", age: 30)

profile
There's Only One Thing To Do: Learn All We Can

0개의 댓글