[안드로이드] 싱글톤 패턴이란?

Lee Jun Hyeong·2023년 1월 9일
0

싱글톤 패턴이란

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다.

예를 들면,

가위 클래스를 객체화해서 무언가를 자르게 되는데 엄마가 쓸 때 새로 객체화하고, 아빠가 쓸 때 새로 객체화하면 가족 수 만큼 가위가 필요하게 되고 이는 결국 집에 가위가 넘쳐나는 현상이 발생하는 원인이 된다.
따라서 가위 클래스를 한 번 객체화 했으면 그 뒤로 엄마, 아빠, 동생, 나, 그 누가 쓰더라도 처음 객체화한 그 가위를 나눠 쓰면 집에 가위가 넘쳐날 일이 없다. 프로그램을 개발할 때도 마찬가지이다. 어떤 곳에서 한 번 객체화를 했으면 다른 곳에서 가위가 필요하면 이미 객체화된 가위를 갖다 쓰면 된다.

왜 사용하는가?

프로그램 전역에서 자주 사용되고 일관된, 정해진 동작을 싱글톤 패턴으로 구현하는 게 훨씬 효과적이다.
어차피 같은 동작을 하는 건데, 계속하여 메모리에 할당하고 해제하며 인스턴스를 사용하기보다, 어디선가 이미 생성해둔 인스턴스를 사용하는 것이 효율적일 수 밖에 없다.

싱글톤 패턴으로 생성된 인스턴스는 고정된 메모리를 사용하기 때문에 필요 없는 메모리 낭비를 방지할 수 있고, 프로그램 전역에서 사용할 수 있는 인스턴스이다 보니 클래스간 데이터 공유도 쉬운 편이다.

장점

  • 인스턴스를 많이 만들면, 불필요한 자원 메모리가 많아진다.
  • 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기가 쉽다. DBCP(DataBase Connection Pool)
  • 싱글톤이 나온 이유이기도 하지만, 인스턴스가 절대적으로 한개만 존재하는 것을 보증하고 싶을 경우 싱글톤을 쓴다.
  • 싱글톤 패턴을 두 번째 이용시부터는 객체 로딩 시간이 현저하게 줄어들어 성능이 좋아진다.

사용 예시

서버와 네트워킹을 하여 데이터를 주고 받기 위해서는, HTTP 통신을 수행하는 객체를 생성하여 사용해야 한다. 편의상 HTTP 통신에 가장 많이 쓰이는 라이브러리인 Retrofit 를 예로 들겠다.
Retrofit 객체는 쉽게 HTTP 메소드를 호출할 수 있도록 도와준다. 따라서 서버의 API 호출을 할 때, Retrofit 객체를 생성하여 이를 수행하면 매우 편리하다.

3 페이지로 구성된 안드로이드 앱이라고 가정해보자. 각 페이지에서는, 각기 다른 HTTP 통신을 수행하여 서버에서 데이터를 가져오거나, 보내는 동작을 수행한다. 그럼 각 페이지에서 Retrofit 객체를 생성하여 HTTP 통신을 하면 될 것이다. 그럼, 아래 그림처럼 동작할 것이다. 오른쪽은 메모리를 표현한 것이다.

중복된 동작을 하는 객체가 3번 생성되다 보니 메모리가 낭비되는 모습을 볼 수 있다.

싱글톤 패턴

최초로 한 번 생성해둔 Retrofit 객체를 사용하면, 어떤 페이지에서든 이를 활용하여 네트워킹 호출을 할 수 있을 것이다. 메모리 면에서 훨씬 효율적이다.

구현

object SingletonObj {

}

코틀린에선 static 개념이 사라지면서 object 라는 개념이 등장한다. object 로 클래스를 정의하게 되면, 클래스를 정의하는 동시에 인스턴스를 생성한다. 이 때, 무조건 단일 인스턴스 생성을 보장한다.

이 클래스 안에 필요한 객체 (e.g. Retrofit 등) , 메소드 등을 정의해두면 프로그램 전역에서 이를 활용할 수 있다.

심지어는 다양한 제약 조건 (멀티 쓰레딩 환경 등) 을 고려해줄 필요 없이, Thread-safe 하고 Lazy 한 초기화를 언어 차원에서 지원해준다. 따라서 개발자는 이러한 것들을 신경쓰지 않아도, 알아서 안전하고 효율적이게 싱글톤이 구현된다.

주의할 점

  • 싱글톤 패턴은 TDD(Test Driven Development)를 할 때 걸림돌이 된다. TDD를 할 때 단위 테스트를 주로 하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야하기 때문이다.
  • 자바에서는 멀티 쓰레딩 환경에서 동기화 처리를 꼭 해줘야 한다. (코틀린을 쓰면 이를 걱정할 필요는 없다.)

의존성 주입으로 결합도를 느슨하게 !

싱글톤 객체가 하는 일이 방대하거나, 많은 데이터를 공유하게 된다면 본의 아니게 이를 사용하는 클래스들과의 결합도가 높아지기 때문에, 개방 폐쇄 원칙 (OCP) 을 위반하게 된다.

이때, 의존성 주입(DI, Dependency Injection)을 통해 모듈간의 결합을 조금 더 느슨하게 만들어 해결할 수 있다.
의존성(=종속성)이란 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미한다.

그림처럼 메인 메인 모듈이 '직접' 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자가 이 부분을 가로채 메인 모듈이 '간접'적으로 의존성을 주입하는 방식이다.
이를 통해 메인 모듈은 하위 모듈에 대한 의존성이 떨어지게 된다. 이를 '디커플링이 된다'라고도 한다.

장점

모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기도 수월하다. 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해진다.

단점

모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 페널티가 생기기도 한다.

의존성 주입 원칙

의존성 주입은 "상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다. 또한, 상위모듈과 하위 모듈 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 한다."라는 의존성 주입 원칙을 지켜야한다.


참고
- https://velog.io/@haero_kim/%ED%98%B9%EC%8B%9C-%EC%8B%B1%EA%B8%80%ED%86%A4%EC%9D%B4%EC%84%B8%EC%9A%94-%EC%A0%80%EB%8A%94-%EB%B2%99%EA%B8%80%ED%86%A4%EC%9D%B4%EC%97%90%EC%9A%94-%E3%85%8B%E3%85%8B%E3%85%8B#1-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4%EC%9D%B4-%EB%AD%90%EC%95%BC-
- https://velog.io/@qwer15417/understanding-singleton-pattern
profile
"왜" 사용하며, "어떻게" 사용하는지에 대해

0개의 댓글