싱글톤 (Singleton) 은 소프트웨어 디자인 패턴 중 하나로, 앱 전역에서 오직 하나의 인스턴스만 살아있는 클래스를 말한다.
Java, Kotlin 과 같이 프로그래밍 언어 차원에서 기본적으로 싱글톤 클래스를 생성할 수 있도록 제공해주는 경우도 있고, Swift 나 Python 처럼 기본 제공을 해주진 않지만 개발자가 싱글톤을 직접 구현하는 경우도 있다.
개발자들의 일반적인 센스에서, 싱글톤을 직접 구현할 때 shared 라는 이름의 객체를 생성한다.
다음은 일반적인 Swift 의 싱글톤 형태다.
// 임의의 과일을 생성하는 싱글톤 클래스.
final class FruitManager {
// 이 싱글톤 인스턴스를 사용하기 위해서는 이 shared 객체를 사용한다.
static let shared = FruitManager()
private init() {}
func getFruit() -> String {
return "사과"
}
}
// 싱글톤을 사용하는 쪽의 코드.
let someFruit = FruitManager.shared.getFruit()
아래처럼 shared 를 쓰지 않고, 클래스 내부에 static 메서드만 만들어 싱글톤을 구현할 수도 있다. 똑같이 앱 전역에서 클래스의 인스턴스는 오직 하나다.
// 임의의 어떤 수를 제공해주는 싱글톤 클래스.
final class FruitManager {
// init 을 못하게 막아버림.
@available(*, unavailable) private init() {}
// static 메서드 선언.
static func getFruit() -> String {
return "사과"
}
}
// 싱글톤을 사용하는 쪽의 코드. shared 만 없을 뿐, 사용성이 같다.
let someFruit = FruitManager.getFruit()
그렇다면 왜 모든 예시들에서는 당연하다는듯이 shared 객체를 만들어 싱글톤을 구현하고 있을까. 그 이유는 아래와 같다.
// 목업을 위한 프로토콜.
protocol FruitManagerProtocol {
func getFruit() -> String
}
// 프로토콜을 채택한 싱글톤 클래스 (1) shared 사용.
final class FruitManager: FruitManagerProtocol {
static let shared = FruitManager()
private init() {}
func getFruit() -> String {
return "사과"
}
}
// 프로토콜을 채택한 싱글톤 클래스 (2) shared 사용 x.
final class FruitManager2: FruitManagerProtocol {
@available(*, unavailable) private init() {}
// static func 으로는 프로토콜을 준수할 수 없음. 에러.
static func getFruit() -> String {
return "사과"
}
}
import Foundation
import XCTest
@testable import Common
final class SomeTest: XCTestCase {
// 목킹한 클래스.
class MockFruitManager: FruitManagerProtocol {
func getFruit() -> String {
return "딸기"
}
}
func testGetFruit() throws {
let fruitManager = MockFruitManager()
let result = fruitManager.getFruit() == "사과"
XCTAssertEqual(result, false)
}
}
FruitManager2 와 같이 shared 를 사용하지 않는 static 한 클래스는 어떻게 활용하면 좋을까. 프로토콜을 채택할 필요없는 순수한 유틸성 클래스에 활용하는 것이 좋다.