1. 싱글톤 패턴 (Singleton)
오직 하나의 객체만 생성하여 상태를 공유하는 싱글톤 패턴을 알아봅니다.
한 줄 요약
프로그램 전체에 딱 하나만 있어야 하는 객체를 클래스가 직접 맡아 두고, 어디서든 같은 문으로만 가져가게 하는 패턴이에요.
문제: 하나여야 하는데 여러 개가 생기는 객체
앱의 설정을 만든다고 해 볼게요. 설정에는 화면에 쓸 언어가 들어 있어요. 화면도, 알림도 이 설정을 봐요. 가장 먼저 떠오르는 방법은 설정이 필요한 곳마다 설정 객체를 새로 만드는 거예요.
[설정]을 설계하자:
'언어'를 [문자열]인 "한국어"로 정하자
(참고: 화면과 알림이 저마다 설정을 새로 만들어요)
[화면]을 설계하자:
'설정'을 [설정]인 새로운 [설정]()으로 정하자
<언어바꾸기>를 만들자 ([문자열]인 '새언어'):
'나'의 '설정'의 '언어'를 '새언어'로 정하자
<보여주기>를 만들자 ():
틀"[화면] 지금 언어: {'나'의 '설정'의 '언어'}"를 출력하자
[알림]을 설계하자:
'설정'을 [설정]인 새로운 [설정]()으로 정하자
<보여주기>를 만들자 ():
틀"[알림] 지금 언어: {'나'의 '설정'의 '언어'}"를 출력하자
'첫화면'을 새로운 [화면]()으로 정하자
'알림창'을 새로운 [알림]()으로 정하자
'첫화면'의 <언어바꾸기>("English")를 실행하자
'첫화면'의 <보여주기>()를 실행하자
'알림창'의 <보여주기>()를 실행하자
실행 결과
[화면] 지금 언어: English
[알림] 지금 언어: 한국어
언어를 영어로 바꿨는데, 알림은 아직 한국어예요. 버그예요.
- 설정이 두 개 생겼어요.
[화면]과[알림]이 각자새로운 [설정]()을 했기 때문에, 이름만 같은 서로 다른 설정 객체를 하나씩 들고 있어요. 한쪽을 바꿔도 다른 쪽은 그대로예요. - 곳이 늘수록 더 어긋나요. 설정을 쓰는 곳이 열 군데면 설정도 열 개예요. 어느 것이 “진짜 설정”인지 아무도 몰라요.
- 에러가 나지 않아요. 프로그램은 멀쩡히 돌아가고, 결과만 조용히 틀려요. 그래서 찾기 어려워요.
해결책: 하나만 만들고, 같은 문으로만 가져가기
싱글톤 패턴은 “하나만 있어야 한다”는 규칙을 클래스가 직접 지키게 해요.
- 클래스가 자기 객체 하나를 정적 멤버(
'우리'의)로 맡아 둬요. 처음에는 비어 있어요. - 객체가 필요하면 누구든
<가져오기>라는 같은 문을 지나요. 처음 부르면 그때 하나를 만들어 맡아 두고, 그다음부터는 맡아 둔 그 객체를 돌려줘요. - 누군가
새로운 [설정]()으로 몰래 하나 더 만들려고 하면, 만들어지는 순간 에러를 내서 막아요.
비유: 학교의 교무실 열쇠
교무실 열쇠는 하나뿐이고, 경비실에 맡겨져 있어요. 선생님들은 필요할 때 경비실에서 받아 가고, 문은 언제나 그 하나의 열쇠로 열어요. 선생님마다 열쇠를 복사해 가면, 누가 문을 잠갔는지, 열쇠가 몇 개인지 아무도 모르게 되겠죠. 경비실이 바로 <가져오기>예요.
누가 무엇을 하나요?
| 역할 | 이 예제에서 | 하는 일 |
|---|---|---|
| 싱글톤 | [설정] |
자기 객체 하나를 맡아 두고, 하나 더 만들어지는 것을 막아요 |
| 맡아 둔 객체 | '우리'의 '_하나' |
프로그램 전체가 함께 쓰는 단 하나의 설정이에요 |
| 하나뿐인 문 | [설정]의 <가져오기>() |
처음에는 만들고, 그다음부터는 같은 것을 돌려줘요 |
| 쓰는 곳 | [화면], [알림] |
설정이 필요할 때마다 같은 문으로 가져가요 |
하리로 만들기
같은 앱을 싱글톤 패턴으로 다시 만들어 볼게요. 마지막에는 몰래 하나 더 만들어 보는 장면도 넣었어요.
(참고: 1단계 — 하나뿐인 설정을 클래스가 직접 맡아 둬요)
[설정]을 설계하자:
'우리'의 '_하나'를 비어있음으로 정하여 숨기자
'언어'를 [문자열]인 "한국어"로 정하자
(참고: 2단계 — 이미 하나 있으면 또 만들지 못하게 막아요)
처음 만들어질 때 () 다음과 같이 하자:
만약 ('우리'의 '_하나'가 비어있음과 다르다) 라면:
새로운 [오류]("설정은 하나만 있어야 해요. [설정]의 <가져오기>()를 쓰세요.")를 발생시키자
(참고: 3단계 — 어디서든 이 문으로만 가져가요)
[설정]을 돌려주는 '우리'의 <가져오기>를 만들자 ():
만약 ('우리'의 '_하나'가 비어있음과 같다) 라면:
'우리'의 '_하나'를 새로운 [설정]()으로 정하자
'우리'의 '_하나'를 돌려주자
[화면]을 설계하자:
<언어바꾸기>를 만들자 ([문자열]인 '새언어'):
[설정]의 <가져오기>()의 '언어'를 '새언어'로 정하자
<보여주기>를 만들자 ():
틀"[화면] 지금 언어: {[설정]의 <가져오기>()의 '언어'}"를 출력하자
[알림]을 설계하자:
<보여주기>를 만들자 ():
틀"[알림] 지금 언어: {[설정]의 <가져오기>()의 '언어'}"를 출력하자
'첫화면'을 새로운 [화면]()으로 정하자
'알림창'을 새로운 [알림]()으로 정하자
'첫화면'의 <언어바꾸기>("English")를 실행하자
'첫화면'의 <보여주기>()를 실행하자
'알림창'의 <보여주기>()를 실행하자
일단 해보자:
'몰래만든설정'을 새로운 [설정]()으로 정하자
오류가 발생했다면 ('에러'):
('에러'의 '메시지')를 출력하자
실행 결과
[화면] 지금 언어: English
[알림] 지금 언어: English
설정은 하나만 있어야 해요. [설정]의 <가져오기>()를 쓰세요.
이번에는 화면에서 바꾼 언어가 알림에도 보여요. 둘이 같은 설정 하나를 보고 있기 때문이에요. 그리고 하나 더 만들려고 하면 그 자리에서 막혀요.
단계별로 다시 보기
- 1단계 — 클래스가 직접 맡아 두기.
'우리'의 '_하나'는 객체마다 따로 생기는 값이 아니라[설정]클래스에 하나만 있는 값이에요.숨기자로 만들어서 밖에서는 직접 바꿀 수 없어요. - 2단계 — 두 번째 탄생 막기. 생성자는
'_하나'가 이미 차 있으면 에러를 내요. 처음<가져오기>가 부를 때는 아직 비어 있어서 무사히 만들어지고, 그다음부터는 누가새로운 [설정]()을 해도 막혀요. - 3단계 — 하나뿐인 문.
<가져오기>는 처음 한 번만 객체를 만들어요. 필요할 때 처음 만드는 이 방식을 늦은 초기화라고 해요. 설정을 한 번도 쓰지 않는 프로그램이라면 설정을 만들지도 않아요.
하리에서는 생성자를 숨길 수 없나요?
네, 하리에는 생성자를숨기자로 만드는 방법이 없어요. 그래서 이 페이지처럼 생성자 안에서 검사해 “하나만”을 지켜요. 덕분에 실수로새로운 [설정]()을 써도 조용히 두 개가 생기지 않고, 무엇을 써야 하는지 알려 주는 에러가 나요.
싱글톤의 그림자, 그리고 더 쉬운 대안
싱글톤은 편하지만, 많이 쓰면 탈이 나는 패턴으로도 유명해요.
- 어디서든 바꿀 수 있는 값이 돼요.
[설정]의 <가져오기>()는 프로그램 어디서나 부를 수 있어서, 누가 언제 언어를 바꿨는지 따라가기 어려워요. - 숨은 연결이 생겨요.
[알림]의 코드만 봐서는 설정을 쓰는지 알 수 없어요. 안에[설정]의 <가져오기>()가 숨어 있거든요. - 따로 시험하기 어려워요. 하나뿐이라서, “영어 설정일 때 알림”과 “한국어 설정일 때 알림”을 한 프로그램에서 나란히 시험하기 어려워요.
그래서 많은 경우 하나만 만들어서 필요한 곳에 건네주는 방법이 더 깔끔해요. 프로그램 맨 처음에 설정을 하나 만들고, 화면과 알림이 만들어질 때 그 설정을 넘겨주는 거예요.
[설정]을 설계하자:
'언어'를 [문자열]인 "한국어"로 정하자
(참고: 설정을 직접 만들지 않고, 만들어질 때 건네받아요)
[화면]을 설계하자:
'설정'을 [설정]인 비어있음으로 정하여 숨기자
처음 만들어질 때 ([설정]인 '설정') 다음과 같이 하자:
'나'의 '설정'을 '설정'으로 정하자
<언어바꾸기>를 만들자 ([문자열]인 '새언어'):
'나'의 '설정'의 '언어'를 '새언어'로 정하자
<보여주기>를 만들자 ():
틀"[화면] 지금 언어: {'나'의 '설정'의 '언어'}"를 출력하자
[알림]을 설계하자:
'설정'을 [설정]인 비어있음으로 정하여 숨기자
처음 만들어질 때 ([설정]인 '설정') 다음과 같이 하자:
'나'의 '설정'을 '설정'으로 정하자
<보여주기>를 만들자 ():
틀"[알림] 지금 언어: {'나'의 '설정'의 '언어'}"를 출력하자
(참고: 프로그램 맨 처음에 하나만 만들어서 나눠 줘요)
'공용설정'을 새로운 [설정]()으로 정하자
'첫화면'을 새로운 [화면]('공용설정')으로 정하자
'알림창'을 새로운 [알림]('공용설정')으로 정하자
'첫화면'의 <언어바꾸기>("English")를 실행하자
'첫화면'의 <보여주기>()를 실행하자
'알림창'의 <보여주기>()를 실행하자
실행 결과
[화면] 지금 언어: English
[알림] 지금 언어: English
결과는 싱글톤과 같아요. 하지만 이번에는 [알림]이 “나는 만들어질 때 설정을 받아요”라고 겉으로 드러내고 있어요. 시험할 때는 다른 설정을 만들어 넘겨주기만 하면 되고요.
어느 쪽을 쓸까요?
넘겨줄 곳이 몇 군데 안 된다면 건네주기가 더 읽기 쉽고 고치기도 쉬워요. 프로그램 아주 깊은 곳까지 넘겨줘야 해서 모든 생성자에 설정이 붙는 게 오히려 번거롭다면, 그때 싱글톤을 쓰세요.
언제 쓰면 좋을까요?
- 프로그램 전체에 정말로 하나만 있어야 하는 것이 있을 때 (설정, 기록장, 연결 하나를 나눠 쓰는 관리자)
- 그 하나를 여러 곳에서 함께 써야 하는데, 일일이 넘겨주기 어려울 때
- 만드는 데 시간이 걸려서, 필요할 때 한 번만 만들고 싶을 때
좋은 점과 아쉬운 점
| 좋은 점 | 아쉬운 점 |
|---|---|
| 어디서 가져가든 같은 객체라서 값이 어긋나지 않아요 | 어디서든 바꿀 수 있어서 누가 바꿨는지 따라가기 어려워요 |
| 실수로 두 번째를 만들면 바로 알려 줘요 | 쓰는 쪽 코드에 연결이 숨어 있어요 |
| 필요할 때 처음 한 번만 만들어요 | 하나뿐이라서 다른 값으로 바꿔 끼워 시험하기 어려워요 |
너무 이른 패턴은 독이에요.
“지금은 하나만 쓰니까”는 싱글톤을 쓸 이유가 아니에요. 하나만 만들어서 넘겨주면 충분한 경우가 훨씬 많아요. 둘 이상 생기면 틀리는 것일 때, 그리고 넘겨주기가 정말 번거로울 때만 싱글톤을 쓰세요.