Programming/Programming Study

[Programming Study] 싱글톤 패턴(Singleton pattern)

hyunjuntyler 2023. 6. 17. 15:20

싱글톤 패턴의 의문 시작

아래와 같이 앱에 햅틱을 사용하다가 의문이 생겼다. 

class Haptic {
    static let instance = Haptic()
    
    func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(type)
    }
    
    func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) {
        let generator = UIImpactFeedbackGenerator(style: style)
        generator.impactOccurred()
    }
}

왜 Haptic을 사용할 때마다 Haptic.instance.impact(type: .soft) 을 사용해야 할까? 왜 instance 가 껴있지?라는 의문을 가지기 시작한 것 같다.

그래 이 클래스는 싱글톤 패턴을 사용하고 있었어! 근데 왜? 싱글톤 패턴이 인스턴스를 하나를 만들어 준다... 관리가 쉽다? 정도만 알고 있는데 정확하게 뭐고 왜 여기서 쓰는 거지? 이제야 제대로 공부하고 싶어졌다. 과연 이 클래스는 싱글톤 패턴을 사용하는 것이 맞을까?!

결론은 저런 클래스는 싱글톤을 사용하는 의미가 없다! 이제 그 이유를 차근차근 알아보도록 하자


싱글톤 패턴 (Singleton pattern)

싱글톤 패턴에 대해서 다시 공부해 보자. 정말 간단하다. 패턴의 이름 그대로 하나의 Class가 하나의 인스턴스를 생성하는 패턴을 의미한다. 그렇다면 매번 인스턴스를 생성해주지 않고, 기존의 만들어 놓은 인스턴스를 활용할 수 있다. 아래와 같이 싱글톤 패턴을 만들어 줄 수 있다.

class Haptic {
    static let instance = Haptic()

    //...
}

모든 곳에서 Hatpic.instance 를 통해 인스턴스로 Haptic 클래스의 동작에 접근할 수 있다! 그리고 여기서 내 코드의 문제점이 하나 나왔다. 싱글톤 패턴을 사용한 이상 이 클래스는 다른 곳에서 인스턴스를 생성하지 못하게 하는 게 맞다. 그렇다면 아래와 같이 코드를 추가해줘야 한다.

class Haptic {
    static let instance = Haptic()
    
    private init() {
        // 외부에서의 인스턴스 생성 방지
    }
    
    // ...
}

private init() { } 을 사용하여 Class를 초기화해줌으로써 외부에서 인스턴스를 생성하는 것을 막아줘야 한다. 벌써 내 코드는 잘못되었다. (그리고 본능적으로 뭔가 비어있는 코드가 추가되면서 클래스의 모양이 이뻐지지 않는다고 생각이 들었다.)

인스턴스를 생성해주려고 하니 Xcode에서 안됩니다~ 라고 오류가 뜬다.

그러면 싱글톤 패턴을 따르는 클래스는 완성이 되었고... 이제 왜 이 싱글톤 패턴을 써야하는가? 의문이 생겼다.

싱글톤 패턴의 장점

싱글톤 패턴의 장점은 뭘까? 인스턴스를 생성할 때마다 메모리 영역을 할당받아야 하지만 인스턴스를 한 번만 생성하기 때문에 메모리 낭비를 막을 수 있다. 그리고 하나의 인스턴스모든 코드에서 적용되고, 쉬운 관리가 가능하다.

싱글톤 패턴의 단점

그렇다면 단점은 무엇일까? 

  • 오류가 생기기 쉽다 : 하나의 인스턴스를 모든 곳에서 쓰기 때문에 만약 클래스가 수정이 된다면 이 인스턴스를 쓰고 있는 곳에서 어떠한 문제들이 생길지 모른다 (위험하다)
  • 테스트가 어렵다 : 처음 만들어준 인스턴스가 계속 유지되어 앱 전체에 영향을 주기 때문에 격리된 환경에서 테스트를 하기 어렵다 (테스트는 개발에서 매우 매우 중요하다)
  • 객체들 간의 의존성이 매우 매우 불투명하게 증가한다 : 인스턴스를 만들 필요 없이 불러오는 것이 너무나도 쉬워져서 이곳저곳에서 많이 사용하게 된다고 한다. 그렇게 되면 많은 객체들이 하나의 인스턴스에 의존하게 되고, 어느 객체에서 쓰고 있는지도 명확하지 않게 된다. 객체 간의 독립성은 매우 매우 중요하다! (이 부분은 좀 더 코딩을 많이 해봐야 와닿을 것 같다)

싱글톤 패턴의 목적은 어떤 시점에도 클래스의 인스턴스가 단 한 개만 존재하도록 보장한다는 것이다. 오로지 편리함 때문이라면 싱글톤 패턴을 사용하는 것을 다시 한번 고려해야 한다.


나의 선택

싱글톤 패턴이란 무서운 패턴이었구나... 를 알게 되었다. 그리고 나의 귀여운(?) Haptic 클래스를 뜯어보기 시작했다.

class Haptic {
    static let instance = Haptic()
        
    private init() {
    }
    
    func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(type)
    }
    
    func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) {
        let generator = UIImpactFeedbackGenerator(style: style)
        generator.impactOccurred()
    }
}

결론은 "왜 굳이...?" 였다. instance 를 일일이 써주면서 심지어 편리함도 가져오지 못했다. 이럴거면 그냥 Haptic().impact(style: .soft) 같은식으로 쓰지... 나의 Haptic 클래스는 Haptic 을 구현하는게 목적이다. 틀릴수도 있겠지만 내 방식대로 리팩토링 해보았다.

class Haptic {
    static func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
        let generator = UINotificationFeedbackGenerator()
        generator.notificationOccurred(type)
    }
    
    static func impact(style: UIImpactFeedbackGenerator.FeedbackStyle) {
        let generator = UIImpactFeedbackGenerator(style: style)
        generator.impactOccurred()
    }
}

나는 데이터 구조를 만들고, 관리해주는 것이 아니라 사용에 목적이 있기 때문에 정적 메서드(static method)가 될 수 있게 하였다. 인스턴스를 할당하지 않고도 동작을 할 수 있게 만들었고 Haptic.impact(.soft) 처럼 쉽게 사용하고 알아보기 쉽게 만들었다.

 

물론 내 방법이 틀릴 수 있고, 점점 고쳐나갈 것이다. 작은 프로젝트이고 규모가 작고 심플한 객체를 사용하기 때문에 다양한 것들을 시도해보고 느껴볼 좋은 기회라고 생각된다.