DesignPatternSmalltalkCompanion:Singleton: Difference between revisions
Onionmixer (talk | contribs) (DPSC SINGLETON 페이지 추가) |
Onionmixer (talk | contribs) (메소드 > 메서드 수정) |
||
(One intermediate revision by the same user not shown) | |||
Line 9: | Line 9: | ||
[[image:dpsc_chapter03_Singleton_01.png]] | [[image:dpsc_chapter03_Singleton_01.png]] | ||
이 다이어그램은 이상적인 싱글톤(Singleton) 구현을 나타내는 동시 유일한 인스턴스를 보장하고 그 인스턴스에 대한 안전한 단일적 접근을 제공한 제공한다. 이 예제에서는 클래스 이름 또한 Singleton으로 하겠다. 다이어그램은 Singleton 클래스의 구조와 프로토콜을 비롯해 (클래스 변수와 클래스 | 이 다이어그램은 이상적인 싱글톤(Singleton) 구현을 나타내는 동시 유일한 인스턴스를 보장하고 그 인스턴스에 대한 안전한 단일적 접근을 제공한 제공한다. 이 예제에서는 클래스 이름 또한 Singleton으로 하겠다. 다이어그램은 Singleton 클래스의 구조와 프로토콜을 비롯해 (클래스 변수와 클래스 메서드) Singleton 인스턴스의 구조 및 프로토콜도 살펴보고자 한다. | ||
Singleton 클래스의 인스턴스는 하나밖에 없기 때문에 클라이언트 객체와 Singleton 클래스 모두 동일한 객체를 참조한다. | Singleton 클래스의 인스턴스는 하나밖에 없기 때문에 클라이언트 객체와 Singleton 클래스 모두 동일한 객체를 참조한다. | ||
Line 21: | Line 21: | ||
스몰토크의 주요 개발환경을 살펴보면 많은 싱글톤 또는 싱글톤과 비슷한 구현들이 흔히 사용되고 있음을 알 수 있다. 이 중 일부는 상기 기준 모두를 엄격하게 만족시키지만 놀랍게도 많은 환경에서는 기준을 충족하지 않는다. 이러한 구현에 대한 오버라이딩 특성은 모든 구현에서 어떤 시점에서든 클래스의 유일한 활성(active) 인스턴스를 위해 제공하는 특성이다. 하지만 모든 구현에서 코드를 명시적으로 통합시켜 동일한 클래스의 다수 인스턴스 생성을 방지하는 것은 아니며, 모든 구현에서 싱글톤 클래스 스스로 통제하는 안전한 접근성을 제공하는 것도 아니다. | 스몰토크의 주요 개발환경을 살펴보면 많은 싱글톤 또는 싱글톤과 비슷한 구현들이 흔히 사용되고 있음을 알 수 있다. 이 중 일부는 상기 기준 모두를 엄격하게 만족시키지만 놀랍게도 많은 환경에서는 기준을 충족하지 않는다. 이러한 구현에 대한 오버라이딩 특성은 모든 구현에서 어떤 시점에서든 클래스의 유일한 활성(active) 인스턴스를 위해 제공하는 특성이다. 하지만 모든 구현에서 코드를 명시적으로 통합시켜 동일한 클래스의 다수 인스턴스 생성을 방지하는 것은 아니며, 모든 구현에서 싱글톤 클래스 스스로 통제하는 안전한 접근성을 제공하는 것도 아니다. | ||
이러한 사례 중 많은 경우는 싱글톤 인스턴스로의 접근이 전역변수(global variable)를 통해 제공된다. 비주얼 스몰토크를 예로 들자면, NotificationManager의 유일한 인스턴스는 Notifier 전역변수에 저장된다. 따라서 클래스 계층구조에 위치한 | 이러한 사례 중 많은 경우는 싱글톤 인스턴스로의 접근이 전역변수(global variable)를 통해 제공된다. 비주얼 스몰토크를 예로 들자면, NotificationManager의 유일한 인스턴스는 Notifier 전역변수에 저장된다. 따라서 클래스 계층구조에 위치한 메서드는 시스템의 윈도우 관리자, 즉 아래 코드에서와 같이 비주얼 스몰토크의 이미지 시작 프로세스에 참여하는 관리자에 접근하고자 할 때 Notifier 전역변수를 하드코딩하게 된다: | ||
<syntaxhighlight lang="smalltalk"> | <syntaxhighlight lang="smalltalk"> | ||
Line 38: | Line 38: | ||
전역변수에 저장된 싱글톤들은 얼마나 위험할까? 비주얼 스몰토크의 OperatingSystemInformationㅡ단일 인스턴스가 global OperatingSystem에 저장되는ㅡ과 같은 클래스의 경우 별로 문제가 되지 않는다; 클래스 이름이 암시하는 바와 같이 클래스의 인스턴스는 정보용 목적에 지나지 않는다. 하나의 인스턴스는 다른 인스턴스와 마찬가지로 정보를 보고할 수 있다 (최근 OS 버전 숫자와 같이). 그러나 Notificationmanager과 같이 새 인스턴스를 생성하고 global Notifier 참조를 가지는 경우는 끔찍할 것이다. NotificationManager는 모든 열린 윈도우를 관리하고, 운영체제 메시지를 적절한 윈도우로 이동시킨다. 새 인스턴스는 윈도우 목록을 빈 목록으로 초기화한다. 따라서 새로운 NotificationManager를 생성시켜 Notifier에 설치하는 것은 Notifier가 더 이상 기존 윈도우를 가리키지 않기 때문에 기존 윈도우를 모두 접근 불가하게 만드는 결과를 낳는다. | 전역변수에 저장된 싱글톤들은 얼마나 위험할까? 비주얼 스몰토크의 OperatingSystemInformationㅡ단일 인스턴스가 global OperatingSystem에 저장되는ㅡ과 같은 클래스의 경우 별로 문제가 되지 않는다; 클래스 이름이 암시하는 바와 같이 클래스의 인스턴스는 정보용 목적에 지나지 않는다. 하나의 인스턴스는 다른 인스턴스와 마찬가지로 정보를 보고할 수 있다 (최근 OS 버전 숫자와 같이). 그러나 Notificationmanager과 같이 새 인스턴스를 생성하고 global Notifier 참조를 가지는 경우는 끔찍할 것이다. NotificationManager는 모든 열린 윈도우를 관리하고, 운영체제 메시지를 적절한 윈도우로 이동시킨다. 새 인스턴스는 윈도우 목록을 빈 목록으로 초기화한다. 따라서 새로운 NotificationManager를 생성시켜 Notifier에 설치하는 것은 Notifier가 더 이상 기존 윈도우를 가리키지 않기 때문에 기존 윈도우를 모두 접근 불가하게 만드는 결과를 낳는다. | ||
단일 클래스 | 단일 클래스 메서드를 통한 싱글톤로의 접근성을 모두 라우팅(routing)하는 것이 global보다 더 선호되는 방식인데, 이는 이 메서드가 단일 접근지점을 제공하기 때문이다. 싱글톤 인스턴스로의 유일한 접근 방법은 클래스로 메시지를 전송하는 방법 뿐이다. 이 메서드에서 클래스는 단일 인스턴스를 책임질뿐 아니라ㅡ인스턴스가 언제 생성되고, 언제 초기화되며, 언제 쓰레기 수집(garbage collection)을 위해 해제되는지ㅡ다수의 인스턴트가 생성되지 않도록 방지하기도 한다. 전역변수는 이 책임 중 어떠한 기능도 하지 못한다. | ||
이러한 단점에도 불구하고 전역변수만을 통해 접근이 가능한 많은 싱글톤들이 다양한 스몰토크 환경에서 사용되고 있으며 사용자들에게 많은 도움을 주고 있다. [디자인 패턴] 편에서는 전역변수를 통해 접근된 싱글톤은 Singleton 패턴의 (DP 127) 예제가 아니라고 언급하였다. 물론 이 예제들은 분명 싱글톤이 맞으며 적절하게 구현되지 않았을 뿐이라고 주장하는 이도 있을 것이다. 결국 이 예제들은 의도 단락에 명시된 싱글톤 기준과 일치한다. 다양한 스몰토크 이미지로부터 기존 예제를 이용하는 데 대한 지속적인 목적을 위해 우리는 그들이 싱글톤을 목적으로 하나 최적에 미치지 못하는 방식으로 구현되었다고 가정할 것이다. | 이러한 단점에도 불구하고 전역변수만을 통해 접근이 가능한 많은 싱글톤들이 다양한 스몰토크 환경에서 사용되고 있으며 사용자들에게 많은 도움을 주고 있다. [디자인 패턴] 편에서는 전역변수를 통해 접근된 싱글톤은 Singleton 패턴의 (DP 127) 예제가 아니라고 언급하였다. 물론 이 예제들은 분명 싱글톤이 맞으며 적절하게 구현되지 않았을 뿐이라고 주장하는 이도 있을 것이다. 결국 이 예제들은 의도 단락에 명시된 싱글톤 기준과 일치한다. 다양한 스몰토크 이미지로부터 기존 예제를 이용하는 데 대한 지속적인 목적을 위해 우리는 그들이 싱글톤을 목적으로 하나 최적에 미치지 못하는 방식으로 구현되었다고 가정할 것이다. | ||
Line 48: | Line 48: | ||
# 영속 싱글톤. 특정 클래스의 인스턴트가 하나만 존재하고, 그 정체성이 절대 변하지 않는다. 예를 들어, NotificationManager에 대한 유일한 인스턴스가 한 번 생성되고 나면 스몰토크 이미지 시작부터 종료까지 지속되며, 항상 클라이언트에 의해 사용되는 단일 인스턴스이다. | # 영속 싱글톤. 특정 클래스의 인스턴트가 하나만 존재하고, 그 정체성이 절대 변하지 않는다. 예를 들어, NotificationManager에 대한 유일한 인스턴스가 한 번 생성되고 나면 스몰토크 이미지 시작부터 종료까지 지속되며, 항상 클라이언트에 의해 사용되는 단일 인스턴스이다. | ||
# 일시 싱글톤. 어느 때건 클래스의 인스턴스가 하나만 존재하지만 인스턴스가 변할 수 있다. 예를 들어, 비주얼 스몰토크 내의 SessionModel은 최근 스몰토크 세션의 상태에 대한 정보를 유지하고, 이미지 시작 및 종료 작업을 처리한다. 새로운 싱글톤 인스턴스가 이미지 시작 시에 생성되어 스몰토크 세션에 걸쳐 사용되기도 한다. | # 일시 싱글톤. 어느 때건 클래스의 인스턴스가 하나만 존재하지만 인스턴스가 변할 수 있다. 예를 들어, 비주얼 스몰토크 내의 SessionModel은 최근 스몰토크 세션의 상태에 대한 정보를 유지하고, 이미지 시작 및 종료 작업을 처리한다. 새로운 싱글톤 인스턴스가 이미지 시작 시에 생성되어 스몰토크 세션에 걸쳐 사용되기도 한다. | ||
# 단일 활성 인스턴스 싱글톤. 이 변형에서는, 어떤 시점에서든 유일한 인스턴스가 활성화되어 있거나 생성될 수 있지만, 다른 유휴 인스턴스(dormant instance)도 존재할 수 있다. 예를 들어, 비주얼웍스 개발환경은 프로그래밍 프로젝트에 대한 개념을 지원한다. 프로젝트는 프로젝트가 활성화되는 동안 이루어진 코드 변경을 추적한다; 프로그래머가 프로젝트를 열어 클래스와 | # 단일 활성 인스턴스 싱글톤. 이 변형에서는, 어떤 시점에서든 유일한 인스턴스가 활성화되어 있거나 생성될 수 있지만, 다른 유휴 인스턴스(dormant instance)도 존재할 수 있다. 예를 들어, 비주얼웍스 개발환경은 프로그래밍 프로젝트에 대한 개념을 지원한다. 프로젝트는 프로젝트가 활성화되는 동안 이루어진 코드 변경을 추적한다; 프로그래머가 프로젝트를 열어 클래스와 메서드를 변경하면 프로젝트는 이러한 변경사항을 모두 기록한다. 각 프로젝트는 Project의 인스턴스로 나타난다. 프로그래밍 환경에 몇 가지 기존 프로젝트가 있지만 한 시점에 하나의 프로젝트만 활성화가 가능하다. 최근 프로그래밍와 관련된 프로젝트 Project 인스턴스는 Project 클래스로부터 접근 가능하다. <br> 이 예제는 다음과 같은 질문을 제기한다: 이것은 싱글톤이 맞는가? 적어도 패턴에 대한 광범위한 해석을 요하긴 한다. Canonical 싱글톤과 같이 클라이언트는 어떤 시점에서든 한 클래스의 단일 인스턴스를 이용 가능하며, 클래스가 그 하나의 인스턴스에 대한 접근성을 제공한다. 단지 이용 불가한 인스턴스들도 있을 뿐이다. 이 예제를 여기는 포함하지만 패턴의 의도를 충분히 읽을 필요가 있겠다. | ||
====예약어==== | ====예약어==== | ||
Line 60: | Line 60: | ||
====유일한 인스턴스 보장하기==== | ====유일한 인스턴스 보장하기==== | ||
싱글톤 클래스는 하나의 인스턴스만 존재하도록 보장하는 책임을 가진다. 이를 위한 안전한 방법은 클라이언트가 새 인스턴스를 생성하지 못하도록 new와 new: 메시지를 이용하는 것이다. 특히 스몰토크는 이러한 메시지를 private으로 만드는 방법을 제공하지 않기 때문에 위의 | 싱글톤 클래스는 하나의 인스턴스만 존재하도록 보장하는 책임을 가진다. 이를 위한 안전한 방법은 클라이언트가 새 인스턴스를 생성하지 못하도록 new와 new: 메시지를 이용하는 것이다. 특히 스몰토크는 이러한 메시지를 private으로 만드는 방법을 제공하지 않기 때문에 위의 메서드들은 클래스를 인스턴스화시키기보다는 오류를 반환해야 한다. | ||
놀랍게도 주오 스몰토크 환경에서 사용되는 싱글톤의 예제 중 다수는 추가 인스턴스 생성에 대한 예방책을 포함하지 않으므로 불안전한 구현을 반영하고 있다. 사용자가 관례를 따라 이 클래스를 인스턴스화하지 않을 것이라는 가정만 할 뿐이다. 안전한 방법은 페이지 DP 130에 구현되어 있는데 이것을 약간만 변경하여 아래 소개하고자 한다: | 놀랍게도 주오 스몰토크 환경에서 사용되는 싱글톤의 예제 중 다수는 추가 인스턴스 생성에 대한 예방책을 포함하지 않으므로 불안전한 구현을 반영하고 있다. 사용자가 관례를 따라 이 클래스를 인스턴스화하지 않을 것이라는 가정만 할 뿐이다. 안전한 방법은 페이지 DP 130에 구현되어 있는데 이것을 약간만 변경하여 아래 소개하고자 한다: | ||
Line 87: | Line 87: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
invalidMessage | invalidMessage 메서드는 walkback을 야기한다. new: 클래스 메서드도 이와 같은 방식으로 구현된다. 따라서 새 UndefinedObject를 생성하려는 시도가 차단되는 것이다. 비주얼웍스에서는 new에 의해 생성된 오류가 nil의 Singletonness를 명시적으로 나타낸다: | ||
<syntaxhighlight lang="smalltalk"> | <syntaxhighlight lang="smalltalk"> | ||
Line 95: | Line 95: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
스몰토크는 다수의 인스턴스 생성을 방지할 수 있도록 런타임 해결책을 필요로 한다. 싱글톤 클래스에 대한 인스턴스 생성 | 스몰토크는 다수의 인스턴스 생성을 방지할 수 있도록 런타임 해결책을 필요로 한다. 싱글톤 클래스에 대한 인스턴스 생성 메서드(new와 같은)가 privat일 경우에만 클라이언트가 클래스의 인스턴스를 생성할 수 없게 된다. 이는 C++에서는 쉽겠지만 스몰토크에서는 불가능하다. [디자인 패턴]편에서 소개한 C++ 예제들은 (DP 129, 131, 132) 생성자가 (인스턴스 생성을 위한 함수) 어떻게 protected로 정의하여 이에 따라 클라이언트가 함수를 호출하거나 새 인스턴스를 생성하지 못하도록 막는지를 설명한다. | ||
스몰토크에는 | 스몰토크에는 메서드로의 접근성을 거부하는 방법이 없다는 점에서 C++의 실제적 이점으로 볼 수 있겠다. 스몰토크 프로그래머들은 메서드에 "private" 라는 라벨을 붙이기 위해 코멘트를 사용하기도 하고 메서드 범주(category)를 지원하는 클래스 브라우저에서 "Private" 범주에 그러한 메서드를 수집하기도 한다. 최신 스몰토크 시스템에서는 메서드를 private으로 선언할 수 있는 환경적 지원이 가능하다. 예를 들어, IBM 스몰토크에 브라우저는 browsing standpoint로부터 public 또는 private 메소들르 구별하기 위해 "public/private" 토글버튼을 포함하기도 한다. 그러나 스몰토크에는 그러한 privacy를 강제실행할 수 있는 언어기반 메커니즘이 없다ㅡ클라이언트가 표면상 private한 메서드를 호출하지 못하도록 막을 수가 없다. | ||
그 결과 스몰토크가 단일 인스턴스를 보장하려면 런타임 해법이 필요하다는 것이다. 다시 언급하지만 이 해법에서는 일반적으로 new를 오버라이딩하는 단순한 과정을 수반하지만 인스턴스에 색인 붙인(indexed) 인스턴스 변수가 포함된 클래스의 경우 new: 또한 오버라이드해야 한다. (Smith, 1996b,는 메시지 수신자가 self임을 검증하는 과정을 수반하는 일반 private | 그 결과 스몰토크가 단일 인스턴스를 보장하려면 런타임 해법이 필요하다는 것이다. 다시 언급하지만 이 해법에서는 일반적으로 new를 오버라이딩하는 단순한 과정을 수반하지만 인스턴스에 색인 붙인(indexed) 인스턴스 변수가 포함된 클래스의 경우 new: 또한 오버라이드해야 한다. (Smith, 1996b,는 메시지 수신자가 self임을 검증하는 과정을 수반하는 일반 private 메서드 문제에 대해 런타임 해법을 제공한다.) | ||
====접근성 제공하기==== | ====접근성 제공하기==== | ||
Line 114: | Line 114: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Singular의 유일한 인스턴스로 접근하고자 하는 모든 클라이언트는 클래스 | Singular의 유일한 인스턴스로 접근하고자 하는 모든 클라이언트는 클래스 메서드 current를 사용해야 한다. new에 대한 기존 구현은 다수의 인스턴스 생성을 방지하는 기능만 하므로 current가 유일한 인스턴스의 생성과 그것으로의 접근을 가능하게 하는 유일한 방법이 된다. 여기서 current는 클래스 변수, UniqueInstance를 준비하기 위해 지연 인스턴스화를 사용하고, 이에 따라 클라이언트가 유일한 인스턴스를 원할 때에만 인스턴스화 비용을 발생하도록 보장한다. 클라이언트가 싱글톤 인스턴스로 메시지를 전송하고 싶다면 다음을 코딩하게 된다: | ||
<syntaxhighlight lang="smalltalk"> | <syntaxhighlight lang="smalltalk"> | ||
Line 122: | Line 122: | ||
====new를 통해 싱글톤에 접근하기?==== | ====new를 통해 싱글톤에 접근하기?==== | ||
싱글톤에 접근하는 대안적 방법으로는 오류를 발생시키기보단 단순히 싱글톤 인스턴스를 반환하도록 new를 정의하는 방법이 있다. 선호되는 접근 | 싱글톤에 접근하는 대안적 방법으로는 오류를 발생시키기보단 단순히 싱글톤 인스턴스를 반환하도록 new를 정의하는 방법이 있다. 선호되는 접근 메서드인 current를 호출함으로써 다음과 같이 이 접근법을 구현한다: | ||
<syntaxhighlight lang="smalltalk"> | <syntaxhighlight lang="smalltalk"> | ||
Line 147: | Line 147: | ||
놀랍게도 이 싱글톤 시나리오의 구현은 앞에서 이미 나타낸 코드와 상당히 비슷할 수 있다. 하위계층구조에서 UniqueInstance 클래스 변수를 이용해 최상위 클래스를 정의하여 하위계층구조에서 유일한 싱글톤을 참조하도록 할 수 있다. 하위계층구조의 모든 클래스는 그 클래스 변수의 값을 공유하기 때문에 효과적일지도 모른다. 따라서 우리가 MazeFactory의 클래스 변수에 싱글톤 미로 팩토리를 설치할 경우 모든 서브클래스는 같은 객체를 참조할 것이다. | 놀랍게도 이 싱글톤 시나리오의 구현은 앞에서 이미 나타낸 코드와 상당히 비슷할 수 있다. 하위계층구조에서 UniqueInstance 클래스 변수를 이용해 최상위 클래스를 정의하여 하위계층구조에서 유일한 싱글톤을 참조하도록 할 수 있다. 하위계층구조의 모든 클래스는 그 클래스 변수의 값을 공유하기 때문에 효과적일지도 모른다. 따라서 우리가 MazeFactory의 클래스 변수에 싱글톤 미로 팩토리를 설치할 경우 모든 서브클래스는 같은 객체를 참조할 것이다. | ||
하나의 계층구조 가지가 있고 그 내부의 각 클래스가 유일한 싱글톤 인스턴스를 가지는 대안적 시나리오를 가정해보자. 단순히 UniqueInstance 클래스 변수를 상속시켜 각 서브클래스마다 재사용할 수는 없다; 각 서브클래스마다 구분된 변수가 필요하기 때문이다. 물론 고유의 클래스 변수로 각 서브클래스를 정의하여 각각에 구분된 접근 | 하나의 계층구조 가지가 있고 그 내부의 각 클래스가 유일한 싱글톤 인스턴스를 가지는 대안적 시나리오를 가정해보자. 단순히 UniqueInstance 클래스 변수를 상속시켜 각 서브클래스마다 재사용할 수는 없다; 각 서브클래스마다 구분된 변수가 필요하기 때문이다. 물론 고유의 클래스 변수로 각 서브클래스를 정의하여 각각에 구분된 접근 메서드를 코딩할 수도 있다. 하지만 특별히 기발하다거나 멋들어진 방법은 아니다. 대신, 최상위클래스에 단일 클래스 인스턴스 변수와 함께 단일 접근 메서드를 정의하고, 모든 서브클래스가 이 구현을 상속받도록 할 수 있다. 각 서브클래스는 변수에 대해 고유의 복사본을 얻어 각각 고유의 싱글톤 인스턴스를 갖게 될 것이다. 단일 접근자 메서드는 메시지의 수신자에 속하는 클래스 인스턴스 변수를 반환할 것이다. | ||
이 접근법을 구현하는 코드는 다음과 같다: | 이 접근법을 구현하는 코드는 다음과 같다: | ||
Line 167: | Line 167: | ||
각 Single 서브클래스는 자동적으로 싱글톤 클래스가 되며, 상위클래스에서 정의된 싱글톤 행위를 상속한다. | 각 Single 서브클래스는 자동적으로 싱글톤 클래스가 되며, 상위클래스에서 정의된 싱글톤 행위를 상속한다. | ||
current는 비교적 관례적인 self new보다는 self basicNew를 이용해 클래스를 인스턴스화시킨다는 사실을 주목하라. 앞에서 소개한 코드에서 보았듯이 new는 오류를 신호로 보내기 때문에 new | current는 비교적 관례적인 self new보다는 self basicNew를 이용해 클래스를 인스턴스화시킨다는 사실을 주목하라. 앞에서 소개한 코드에서 보았듯이 new는 오류를 신호로 보내기 때문에 new 메서드를 호출할 수는 없다. 전형적 대안책으로 super new가 있는데 이는 current를 상속하는 Single의 서브클래스가 Single class>>new를 호출하면서 끝나기 때문에 문제가 발생하며, 우리가 직면할 문제와 거의 일치한다. | ||
====접근 | ====접근 메서드 naming하기==== | ||
싱글톤 클래스는 접근자 | 싱글톤 클래스는 접근자 메서드를 통해 유일한 인스턴스로의 접근성을 제공하는데 과연 이 메서드는 무엇이라고 불러야 할까? 프로그래머들은 이 메서드를 주로 default나 current라고 부르곤 한다ㅡ예를 들어 비주얼 스몰토크에서는 SourceManager current, 비주얼웍스에서는 Screen default라고 부른다. | ||
current 이름은 클래스에 다수의 인스턴스가 존재한다는 의미를 가지기도 한다: 하나만 활성화되고 나머지는 유휴 상태이다. 이러한 현상은 클래스가 유일하게 활성화된 인스턴스 싱글톤일 때 적절하겠다. 하지만 이에만 의존할 수 없는 것이, 상기 표시한 코드에서와 같이 비주얼 스몰토크의 많은 transient 싱글톤 클래스가 current 메시지를 사용하기 때문이다. | current 이름은 클래스에 다수의 인스턴스가 존재한다는 의미를 가지기도 한다: 하나만 활성화되고 나머지는 유휴 상태이다. 이러한 현상은 클래스가 유일하게 활성화된 인스턴스 싱글톤일 때 적절하겠다. 하지만 이에만 의존할 수 없는 것이, 상기 표시한 코드에서와 같이 비주얼 스몰토크의 많은 transient 싱글톤 클래스가 current 메시지를 사용하기 때문이다. | ||
default 이름 또한 오해의 소지가 있다. 어떤 클래스는 싱글톤 인스턴스가 아닌, 실제 디폴트(default) 값을 제공하기 위해 사용한다. 비주얼웍스를 예로 들면, Time default는 당시 시계가 가리키는 시각을 포함하는 Time 객체를 검색한다; Date current에서도 마찬가지다. 하지만 둘다 싱글톤이 아니다. 여기서 까다로운 점은 자연언어가 모호하다는 사실이다: default 와 같은 단어들이 다르게 해석될 수 있다. Fowler (1997)가 주장한 바와 같이 이름 지정은 객체지향 분석에서 까다로운 문제들 중 하나이며 | default 이름 또한 오해의 소지가 있다. 어떤 클래스는 싱글톤 인스턴스가 아닌, 실제 디폴트(default) 값을 제공하기 위해 사용한다. 비주얼웍스를 예로 들면, Time default는 당시 시계가 가리키는 시각을 포함하는 Time 객체를 검색한다; Date current에서도 마찬가지다. 하지만 둘다 싱글톤이 아니다. 여기서 까다로운 점은 자연언어가 모호하다는 사실이다: default 와 같은 단어들이 다르게 해석될 수 있다. Fowler (1997)가 주장한 바와 같이 이름 지정은 객체지향 분석에서 까다로운 문제들 중 하나이며 "어떠한 이름도 완벽하지 않다" (p.9). | ||
[디자인 패턴]에서는 instance; | [디자인 패턴]에서는 instance; 메서드를 단순하게 부를 것을 권한다; Buschmann et al. (1996)는 getInstance를 추천하였다. 아마도 검색된 객체, 즉 defaultNotifier나 currentSourceManager와 같이 객체에 대한 더 많은 정보를 제공하는 이름을 사용해야 할지도 모른다. 이는 Beck (1997)이 Intention Revealing Selector라는 부르는 것을 적용시킨 것이다. 그러나 많은 저자들은 SourceManager currentSourceManager와 같은 식은 불필요하게 길며 오히려 정해진 스몰토크의 협약(convention)이 (예: SourceManager current 또는 Screen default) 더 적절하다고 주장할 것이다. | ||
가장 좋은 충고는 공통된 방식을 사용하여 이름을 정하고, 항상 한 걸음 물러나 자신의 코드를 반영하며, 다른 누군가 자신의 코드를 읽고 이해할 것이라는 사실을 명심하는 것이다. | 가장 좋은 충고는 공통된 방식을 사용하여 이름을 정하고, 항상 한 걸음 물러나 자신의 코드를 반영하며, 다른 누군가 자신의 코드를 읽고 이해할 것이라는 사실을 명심하는 것이다. | ||
====클래스 | ====클래스 메서드만?==== | ||
클래스가 하나의 인스턴스만 가져야 할 경우 스몰토크 프로그래머는 클래스 | 클래스가 하나의 인스턴스만 가져야 할 경우 스몰토크 프로그래머는 클래스 메서드처럼 그의 모든 행위를 구현하고 싶을 때가 있다. 클래스 객체는 메시지 전송 및 수신 능력을 가지기 때문에 인스턴스와 인스턴스 메서드를 가지지 않도록 클래스를 설계할 수도 있고 클래스 측면에 전체적인 싱글톤 프로토콜을 구현할 수도 있다 (클래스 메서드와 같이). 사실상 스몰토크/V의 이전 버전에서 (스몰토크 익스프레스도 포함) Compiler 클래스가 구현한 것과 마찬가지로 비주얼 스몰토크의 OLESessionmanager와 OLERegistryInterface 클래스는 위의 방식으로 그 행위를 구현한다. | ||
객체를 완전히 클래스 측면에서 구현하는 것을 피하는 두 가지 이유가 있는데, 하나는 이론적인 이유고 하나는 실용적 이유에서이다. 이론적 이유는 그것이 메타클래스 프로토콜을 자연스럽지 못한 방식으로 확장한다는 것이다. | 객체를 완전히 클래스 측면에서 구현하는 것을 피하는 두 가지 이유가 있는데, 하나는 이론적인 이유고 하나는 실용적 이유에서이다. 이론적 이유는 그것이 메타클래스 프로토콜을 자연스럽지 못한 방식으로 확장한다는 것이다. "일반 객체는 실세계를 모델화하는데 사용된다. 메타객체는 이러한 일반 객체를 설명한다." (Rivard, 1997). 클래스는 메타객체ㅡ도메인 객체보다는 스몰토크 환경의 일부ㅡ이어야 한다. 이들의 주요 역할로는 인스턴스 생성, 인스턴스로 접근 (싱글톤에서와 마찬가지로), 그리고 클래스의 모든 인스턴스에 공통되는 리소스 공유가 포함된다. 이러한 역할들은 클래스 메서드에서 구현된다. 인스턴스 메서드와 클래스 메서드의 구분은 행위의 명세로부터 인스턴스의 생성 및 관리를 분리시킨다. 클래스 메서드에 의해 구현된 도메인 특정적 또는 애플리케이션 특정적 행위는 이러한 구분을 흐리게 만든다. 이러한 종류의 행위는 인스턴스 측면에 속한다. | ||
클래스 측면에서의 구현을 피하는 실용적 이유는 확장성과 관련된다: 후에 시스템이 하나 이상의 객체 인스턴스를 필요로 할 경우 설계를 유지하고 확장하기가 힘들 것이다. 시스템이 클래스 메시지를 통해 객체를 처리하는 코드가 많아진 경우, 비싱글톤 (nonSingleton) 설계로 전환하게 되면 모든 코드를 찾아 수정해야 함을 의미한다. 이러한 변경은 인스턴스 | 클래스 측면에서의 구현을 피하는 실용적 이유는 확장성과 관련된다: 후에 시스템이 하나 이상의 객체 인스턴스를 필요로 할 경우 설계를 유지하고 확장하기가 힘들 것이다. 시스템이 클래스 메시지를 통해 객체를 처리하는 코드가 많아진 경우, 비싱글톤 (nonSingleton) 설계로 전환하게 되면 모든 코드를 찾아 수정해야 함을 의미한다. 이러한 변경은 인스턴스 메서드에 의해 싱글톤의 행위가 이미 구현되어 있을 때 훨씬 수월할 것이다. | ||
===예제 코드=== | ===예제 코드=== | ||
Line 263: | Line 263: | ||
비주얼웍스에서 SourceFileManager의 싱글톤 인스턴스는 코드 파일을 추적한다 (소스와 변경 파일). 비주얼 스몰토크에는 SourceManager current로 접근되는 유사한 이름의 클래스 SourceManager가 있다. | 비주얼웍스에서 SourceFileManager의 싱글톤 인스턴스는 코드 파일을 추적한다 (소스와 변경 파일). 비주얼 스몰토크에는 SourceManager current로 접근되는 유사한 이름의 클래스 SourceManager가 있다. | ||
비주얼 스몰토크에서 싱글톤 SessionModel은 최근 스몰토크 세션에 관련된 정보를 추적한다. 이는 Sessionmodel current라는 메시지를 이용해 접근된다. | 비주얼 스몰토크에서 싱글톤 SessionModel은 최근 스몰토크 세션에 관련된 정보를 추적한다. 이는 Sessionmodel current라는 메시지를 이용해 접근된다. | ||
Line 270: | Line 271: | ||
<sup>Single-Actgive Instance Singletone</sup> | <sup>Single-Actgive Instance Singletone</sup> | ||
비주얼웍스는 개발자가 원하는 수 만큼의 Project 를 관리할 수 있지만 한 번에 활성화되어 있는 프로젝트는 하나뿐이다 (Project current). 최근 실행한 프로젝트는 최근 변경 집합과 (ChangeSet current) 최근 표시된 윈도우 집합을 (ControlManager의 인스턴스인 global ScheduledControllers에 유지되는) 적는다. 이 모든 클래스는 다수의 인스턴스를 가지지만 한 번에 하나의 인스턴스만 | 비주얼웍스는 개발자가 원하는 수 만큼의 Project 를 관리할 수 있지만 한 번에 활성화되어 있는 프로젝트는 하나뿐이다 (Project current). 최근 실행한 프로젝트는 최근 변경 집합과 (ChangeSet current) 최근 표시된 윈도우 집합을 (ControlManager의 인스턴스인 global ScheduledControllers에 유지되는) 적는다. 이 모든 클래스는 다수의 인스턴스를 가지지만 한 번에 하나의 인스턴스만 "살아 있다." | ||
====하위계층구조에서 클래스별 싱글톤==== | ====하위계층구조에서 클래스별 싱글톤==== | ||
비주얼 스몰토크에서 DynamicLinkLibrary는 외부 동적 링크 라이브러리 (DLL) 내의 코드와 스몰토크 간 의사소통을 구현하는 추상 클래스이다. 특정 DLL 파일은 고유의 구체적 서브클래스를 통해서 접근된다. 추상 클래스는 각 서브클래스에 대한 싱글톤 인스턴스를 저장하기 위해 클래스 인스턴스 변수와 (current) 함께 동일한 이름의 해당 접근자 | 비주얼 스몰토크에서 DynamicLinkLibrary는 외부 동적 링크 라이브러리 (DLL) 내의 코드와 스몰토크 간 의사소통을 구현하는 추상 클래스이다. 특정 DLL 파일은 고유의 구체적 서브클래스를 통해서 접근된다. 추상 클래스는 각 서브클래스에 대한 싱글톤 인스턴스를 저장하기 위해 클래스 인스턴스 변수와 (current) 함께 동일한 이름의 해당 접근자 메서드를 정의한다. 따라서 current를 어떤 서브클래스로 전송하면 (예: GDIDLL current) 그 클래스의 싱글톤 인스턴스를 검색한다. | ||
==Notes== | ==Notes== |
Latest revision as of 10:50, 8 January 2013
SINGLETON (DP 127)
의도
한 클래스에 인스턴스는 하나임을 보장하고 이 클래스에 대한 전역적 접근지점을 제공한다.
구조
이 다이어그램은 이상적인 싱글톤(Singleton) 구현을 나타내는 동시 유일한 인스턴스를 보장하고 그 인스턴스에 대한 안전한 단일적 접근을 제공한 제공한다. 이 예제에서는 클래스 이름 또한 Singleton으로 하겠다. 다이어그램은 Singleton 클래스의 구조와 프로토콜을 비롯해 (클래스 변수와 클래스 메서드) Singleton 인스턴스의 구조 및 프로토콜도 살펴보고자 한다.
Singleton 클래스의 인스턴스는 하나밖에 없기 때문에 클라이언트 객체와 Singleton 클래스 모두 동일한 객체를 참조한다.
논의
때로는 어떤 주어진 시간에 특정 클래스의 인스턴스가 하나만 존재하는 것이 불가피하거나 바람직한 경우가 있다. 예를 들어, 스몰토크 환경에서 모든 윈도우에 대한 관리자를 하나만 원하는 경우가 있다. 싱글톤은 이를 가능하게 해준다. 이 패턴은 클래스가 유일한 인스턴스(sole instance)의 추적을 책임지며, 그 인스턴스로 유일한 접근을 제공하고 다수의 인스턴스 생성을 방지한다.
스몰토크의 주요 개발환경을 살펴보면 많은 싱글톤 또는 싱글톤과 비슷한 구현들이 흔히 사용되고 있음을 알 수 있다. 이 중 일부는 상기 기준 모두를 엄격하게 만족시키지만 놀랍게도 많은 환경에서는 기준을 충족하지 않는다. 이러한 구현에 대한 오버라이딩 특성은 모든 구현에서 어떤 시점에서든 클래스의 유일한 활성(active) 인스턴스를 위해 제공하는 특성이다. 하지만 모든 구현에서 코드를 명시적으로 통합시켜 동일한 클래스의 다수 인스턴스 생성을 방지하는 것은 아니며, 모든 구현에서 싱글톤 클래스 스스로 통제하는 안전한 접근성을 제공하는 것도 아니다.
이러한 사례 중 많은 경우는 싱글톤 인스턴스로의 접근이 전역변수(global variable)를 통해 제공된다. 비주얼 스몰토크를 예로 들자면, NotificationManager의 유일한 인스턴스는 Notifier 전역변수에 저장된다. 따라서 클래스 계층구조에 위치한 메서드는 시스템의 윈도우 관리자, 즉 아래 코드에서와 같이 비주얼 스몰토크의 이미지 시작 프로세스에 참여하는 관리자에 접근하고자 할 때 Notifier 전역변수를 하드코딩하게 된다:
SessionModel>>startupWindowSystem
"Private - perform OS window system startup."
| oldWindows |
...
Notifier initializeWindowHandles.
...
oldWindows := Notifier windows.
Notifier initialize.
...
^oldWindows
전역변수에 저장된 싱글톤들은 얼마나 위험할까? 비주얼 스몰토크의 OperatingSystemInformationㅡ단일 인스턴스가 global OperatingSystem에 저장되는ㅡ과 같은 클래스의 경우 별로 문제가 되지 않는다; 클래스 이름이 암시하는 바와 같이 클래스의 인스턴스는 정보용 목적에 지나지 않는다. 하나의 인스턴스는 다른 인스턴스와 마찬가지로 정보를 보고할 수 있다 (최근 OS 버전 숫자와 같이). 그러나 Notificationmanager과 같이 새 인스턴스를 생성하고 global Notifier 참조를 가지는 경우는 끔찍할 것이다. NotificationManager는 모든 열린 윈도우를 관리하고, 운영체제 메시지를 적절한 윈도우로 이동시킨다. 새 인스턴스는 윈도우 목록을 빈 목록으로 초기화한다. 따라서 새로운 NotificationManager를 생성시켜 Notifier에 설치하는 것은 Notifier가 더 이상 기존 윈도우를 가리키지 않기 때문에 기존 윈도우를 모두 접근 불가하게 만드는 결과를 낳는다.
단일 클래스 메서드를 통한 싱글톤로의 접근성을 모두 라우팅(routing)하는 것이 global보다 더 선호되는 방식인데, 이는 이 메서드가 단일 접근지점을 제공하기 때문이다. 싱글톤 인스턴스로의 유일한 접근 방법은 클래스로 메시지를 전송하는 방법 뿐이다. 이 메서드에서 클래스는 단일 인스턴스를 책임질뿐 아니라ㅡ인스턴스가 언제 생성되고, 언제 초기화되며, 언제 쓰레기 수집(garbage collection)을 위해 해제되는지ㅡ다수의 인스턴트가 생성되지 않도록 방지하기도 한다. 전역변수는 이 책임 중 어떠한 기능도 하지 못한다.
이러한 단점에도 불구하고 전역변수만을 통해 접근이 가능한 많은 싱글톤들이 다양한 스몰토크 환경에서 사용되고 있으며 사용자들에게 많은 도움을 주고 있다. [디자인 패턴] 편에서는 전역변수를 통해 접근된 싱글톤은 Singleton 패턴의 (DP 127) 예제가 아니라고 언급하였다. 물론 이 예제들은 분명 싱글톤이 맞으며 적절하게 구현되지 않았을 뿐이라고 주장하는 이도 있을 것이다. 결국 이 예제들은 의도 단락에 명시된 싱글톤 기준과 일치한다. 다양한 스몰토크 이미지로부터 기존 예제를 이용하는 데 대한 지속적인 목적을 위해 우리는 그들이 싱글톤을 목적으로 하나 최적에 미치지 못하는 방식으로 구현되었다고 가정할 것이다.
싱글톤 변형
주요 스몰토크 개발환경에서 사용되는 Singleton 패턴의 3가지 변형을 살펴보았다ㅡ영속, 일시, 단일 활성 인스턴스가 그것이다. 아래에 이 3가지 변형을 각각 설명하고자 한다:
- 영속 싱글톤. 특정 클래스의 인스턴트가 하나만 존재하고, 그 정체성이 절대 변하지 않는다. 예를 들어, NotificationManager에 대한 유일한 인스턴스가 한 번 생성되고 나면 스몰토크 이미지 시작부터 종료까지 지속되며, 항상 클라이언트에 의해 사용되는 단일 인스턴스이다.
- 일시 싱글톤. 어느 때건 클래스의 인스턴스가 하나만 존재하지만 인스턴스가 변할 수 있다. 예를 들어, 비주얼 스몰토크 내의 SessionModel은 최근 스몰토크 세션의 상태에 대한 정보를 유지하고, 이미지 시작 및 종료 작업을 처리한다. 새로운 싱글톤 인스턴스가 이미지 시작 시에 생성되어 스몰토크 세션에 걸쳐 사용되기도 한다.
- 단일 활성 인스턴스 싱글톤. 이 변형에서는, 어떤 시점에서든 유일한 인스턴스가 활성화되어 있거나 생성될 수 있지만, 다른 유휴 인스턴스(dormant instance)도 존재할 수 있다. 예를 들어, 비주얼웍스 개발환경은 프로그래밍 프로젝트에 대한 개념을 지원한다. 프로젝트는 프로젝트가 활성화되는 동안 이루어진 코드 변경을 추적한다; 프로그래머가 프로젝트를 열어 클래스와 메서드를 변경하면 프로젝트는 이러한 변경사항을 모두 기록한다. 각 프로젝트는 Project의 인스턴스로 나타난다. 프로그래밍 환경에 몇 가지 기존 프로젝트가 있지만 한 시점에 하나의 프로젝트만 활성화가 가능하다. 최근 프로그래밍와 관련된 프로젝트 Project 인스턴스는 Project 클래스로부터 접근 가능하다.
이 예제는 다음과 같은 질문을 제기한다: 이것은 싱글톤이 맞는가? 적어도 패턴에 대한 광범위한 해석을 요하긴 한다. Canonical 싱글톤과 같이 클라이언트는 어떤 시점에서든 한 클래스의 단일 인스턴스를 이용 가능하며, 클래스가 그 하나의 인스턴스에 대한 접근성을 제공한다. 단지 이용 불가한 인스턴스들도 있을 뿐이다. 이 예제를 여기는 포함하지만 패턴의 의도를 충분히 읽을 필요가 있겠다.
예약어
스몰토크는 언어 자체에 구축된 ‘특별한 경우를 위한 싱글톤과 같은 예약어’(special-case Singleton-like reserved literals)을 3개, nil, true, false를 제공하기도 한다. 이에 해당하는 클래스로 메시지를 전송하여 객체에 접근하는 것은 아니지만 Singleton 패턴의 의도와 일치한다: 이들의 클래스ㅡUndefinedObject, True, Falseㅡ각각은 하나의 인스턴스만 가지며, 모두 추가 인스턴스 생성을 방지한다.
구현
싱글톤의 구현 문제는 의도 단락에 명시된 2가지 기준과 관련이 있다: 클래스에 하나의 인스턴스만 보장하고, 그에 대해 전역적 접근지점을 제공한다.
유일한 인스턴스 보장하기
싱글톤 클래스는 하나의 인스턴스만 존재하도록 보장하는 책임을 가진다. 이를 위한 안전한 방법은 클라이언트가 새 인스턴스를 생성하지 못하도록 new와 new: 메시지를 이용하는 것이다. 특히 스몰토크는 이러한 메시지를 private으로 만드는 방법을 제공하지 않기 때문에 위의 메서드들은 클래스를 인스턴스화시키기보다는 오류를 반환해야 한다.
놀랍게도 주오 스몰토크 환경에서 사용되는 싱글톤의 예제 중 다수는 추가 인스턴스 생성에 대한 예방책을 포함하지 않으므로 불안전한 구현을 반영하고 있다. 사용자가 관례를 따라 이 클래스를 인스턴스화하지 않을 것이라는 가정만 할 뿐이다. 안전한 방법은 페이지 DP 130에 구현되어 있는데 이것을 약간만 변경하여 아래 소개하고자 한다:
Object subclass: #Singular
instanceVariableNames: ''
classVariableNames: 'UniqueInstance'
poolDictionaries: ''
Singular class>>new
"Override the inherited 'new' to assure there
is never more more than one instance of me."
self error: 'Class ', self name,
' cannot create new instances'
기존 싱글톤 예제에서는 동일한 유형의 예방적 접근법을 이용한다. 비주얼 스몰토크를 예로 들면 UndefinedObject가 아래와 같이 new를 오버라이드한다:
UndefinedObject class>>new
"Create a new instance of the receiver. Disallowed
for this class because there is only a single
instance, nil."
^self invalidMessage
invalidMessage 메서드는 walkback을 야기한다. new: 클래스 메서드도 이와 같은 방식으로 구현된다. 따라서 새 UndefinedObject를 생성하려는 시도가 차단되는 것이다. 비주얼웍스에서는 new에 의해 생성된 오류가 nil의 Singletonness를 명시적으로 나타낸다:
UndefinedObject class>>new
self error: 'You may not create any more undefined objects--
use nil'
스몰토크는 다수의 인스턴스 생성을 방지할 수 있도록 런타임 해결책을 필요로 한다. 싱글톤 클래스에 대한 인스턴스 생성 메서드(new와 같은)가 privat일 경우에만 클라이언트가 클래스의 인스턴스를 생성할 수 없게 된다. 이는 C++에서는 쉽겠지만 스몰토크에서는 불가능하다. [디자인 패턴]편에서 소개한 C++ 예제들은 (DP 129, 131, 132) 생성자가 (인스턴스 생성을 위한 함수) 어떻게 protected로 정의하여 이에 따라 클라이언트가 함수를 호출하거나 새 인스턴스를 생성하지 못하도록 막는지를 설명한다.
스몰토크에는 메서드로의 접근성을 거부하는 방법이 없다는 점에서 C++의 실제적 이점으로 볼 수 있겠다. 스몰토크 프로그래머들은 메서드에 "private" 라는 라벨을 붙이기 위해 코멘트를 사용하기도 하고 메서드 범주(category)를 지원하는 클래스 브라우저에서 "Private" 범주에 그러한 메서드를 수집하기도 한다. 최신 스몰토크 시스템에서는 메서드를 private으로 선언할 수 있는 환경적 지원이 가능하다. 예를 들어, IBM 스몰토크에 브라우저는 browsing standpoint로부터 public 또는 private 메소들르 구별하기 위해 "public/private" 토글버튼을 포함하기도 한다. 그러나 스몰토크에는 그러한 privacy를 강제실행할 수 있는 언어기반 메커니즘이 없다ㅡ클라이언트가 표면상 private한 메서드를 호출하지 못하도록 막을 수가 없다.
그 결과 스몰토크가 단일 인스턴스를 보장하려면 런타임 해법이 필요하다는 것이다. 다시 언급하지만 이 해법에서는 일반적으로 new를 오버라이딩하는 단순한 과정을 수반하지만 인스턴스에 색인 붙인(indexed) 인스턴스 변수가 포함된 클래스의 경우 new: 또한 오버라이드해야 한다. (Smith, 1996b,는 메시지 수신자가 self임을 검증하는 과정을 수반하는 일반 private 메서드 문제에 대해 런타임 해법을 제공한다.)
접근성 제공하기
몇몇 싱글톤 구현에서는 전역변수를 통해 접근성을 제공하는 것을 살펴보았다. 좀 더 안전한 접근법으로, 싱글톤의 클래스 객체가 책임을 지고 자신을 통해 접근을 가능하게 하는 것이다. 방금 우리가 정의한 클래스의 경우 그 접근은 다음과 같을 것이다.
Singular class>>current
"Return the Singleton instance for this class;
if it hasn't been created yet, do so now."
UniqueInstance isNil
ifTrue: [UniqueInstance := self basicNew].
^UniqueInstance
Singular의 유일한 인스턴스로 접근하고자 하는 모든 클라이언트는 클래스 메서드 current를 사용해야 한다. new에 대한 기존 구현은 다수의 인스턴스 생성을 방지하는 기능만 하므로 current가 유일한 인스턴스의 생성과 그것으로의 접근을 가능하게 하는 유일한 방법이 된다. 여기서 current는 클래스 변수, UniqueInstance를 준비하기 위해 지연 인스턴스화를 사용하고, 이에 따라 클라이언트가 유일한 인스턴스를 원할 때에만 인스턴스화 비용을 발생하도록 보장한다. 클라이언트가 싱글톤 인스턴스로 메시지를 전송하고 싶다면 다음을 코딩하게 된다:
Singular current someMessage.
new를 통해 싱글톤에 접근하기?
싱글톤에 접근하는 대안적 방법으로는 오류를 발생시키기보단 단순히 싱글톤 인스턴스를 반환하도록 new를 정의하는 방법이 있다. 선호되는 접근 메서드인 current를 호출함으로써 다음과 같이 이 접근법을 구현한다:
Singular class>>new
^self current
하지만 이 접근법은 매우 혼란스러울 수 있다. new의 관례적 의미(conventional semantics)는 이전에 존재하지 않은 인스턴스를 생성시켜 반환한다는 뜻을 담고 있다. 기존 인스턴스를 대신 반환 시 잘못된 것이다. 다음 코드는 잠재적 위험을 나타낸다:
| roadRunner wileECoyote |
roadRunner := SingleToon new.
wileECoyote := SingleToon new.
roadRunner position: 100@200.
wileECoyote position: 200@300.
이 코드를 읽은 독자들은 (또는 저자들은) roadRunner와 wileECoyote가 동일한 객체ㅡSingleToon의 싱글톤 인스턴스ㅡ를 참조하는지 확실히 알 수가 없다. 마지막 라인은 roadRunner에 의해 참조된 객체를 수정하는데, 프로그래머는 아마도 이를 의도하진 않았을 것이다.
단일 하위계층구조의 싱글톤
우리에게 클래스의 논리적 그루핑ㅡ일반적으로 클래스 계층구조의 가지ㅡ이 있는데 클래스별이 아니라 전체 그룹에 대해 하나의 싱글톤 인스턴스만 원하는 경우도 있다. [디자인 패턴] 편에서는 미로(maze) 팩토리 클래스별로 가지는 것이 아니라 전체 시스템에 단일 미로 팩토리를 가지는 컨텍스트에서 이러한 개념을 살펴보았다. 페이지 DP 133에 실린 예제는 환경변수의 스트링(string)에 따라 MazeFactory의 새 인스턴스 또는 그 서브클래스의 인스턴스를 생성한다. 따라서 생성된 객체는 유일한 미로 팩토리와 마찬가지로 MazeFactory 클래스에 설치된다.
놀랍게도 이 싱글톤 시나리오의 구현은 앞에서 이미 나타낸 코드와 상당히 비슷할 수 있다. 하위계층구조에서 UniqueInstance 클래스 변수를 이용해 최상위 클래스를 정의하여 하위계층구조에서 유일한 싱글톤을 참조하도록 할 수 있다. 하위계층구조의 모든 클래스는 그 클래스 변수의 값을 공유하기 때문에 효과적일지도 모른다. 따라서 우리가 MazeFactory의 클래스 변수에 싱글톤 미로 팩토리를 설치할 경우 모든 서브클래스는 같은 객체를 참조할 것이다.
하나의 계층구조 가지가 있고 그 내부의 각 클래스가 유일한 싱글톤 인스턴스를 가지는 대안적 시나리오를 가정해보자. 단순히 UniqueInstance 클래스 변수를 상속시켜 각 서브클래스마다 재사용할 수는 없다; 각 서브클래스마다 구분된 변수가 필요하기 때문이다. 물론 고유의 클래스 변수로 각 서브클래스를 정의하여 각각에 구분된 접근 메서드를 코딩할 수도 있다. 하지만 특별히 기발하다거나 멋들어진 방법은 아니다. 대신, 최상위클래스에 단일 클래스 인스턴스 변수와 함께 단일 접근 메서드를 정의하고, 모든 서브클래스가 이 구현을 상속받도록 할 수 있다. 각 서브클래스는 변수에 대해 고유의 복사본을 얻어 각각 고유의 싱글톤 인스턴스를 갖게 될 것이다. 단일 접근자 메서드는 메시지의 수신자에 속하는 클래스 인스턴스 변수를 반환할 것이다.
이 접근법을 구현하는 코드는 다음과 같다:
Object subclass: #Single
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
Single class instanceVariableNames: 'uniqueInstance'
Single class>>current
uniqueInstance isNil
ifTrue: [uniqueInstance := self basicNew].
^uniqueInstance
각 Single 서브클래스는 자동적으로 싱글톤 클래스가 되며, 상위클래스에서 정의된 싱글톤 행위를 상속한다.
current는 비교적 관례적인 self new보다는 self basicNew를 이용해 클래스를 인스턴스화시킨다는 사실을 주목하라. 앞에서 소개한 코드에서 보았듯이 new는 오류를 신호로 보내기 때문에 new 메서드를 호출할 수는 없다. 전형적 대안책으로 super new가 있는데 이는 current를 상속하는 Single의 서브클래스가 Single class>>new를 호출하면서 끝나기 때문에 문제가 발생하며, 우리가 직면할 문제와 거의 일치한다.
접근 메서드 naming하기
싱글톤 클래스는 접근자 메서드를 통해 유일한 인스턴스로의 접근성을 제공하는데 과연 이 메서드는 무엇이라고 불러야 할까? 프로그래머들은 이 메서드를 주로 default나 current라고 부르곤 한다ㅡ예를 들어 비주얼 스몰토크에서는 SourceManager current, 비주얼웍스에서는 Screen default라고 부른다.
current 이름은 클래스에 다수의 인스턴스가 존재한다는 의미를 가지기도 한다: 하나만 활성화되고 나머지는 유휴 상태이다. 이러한 현상은 클래스가 유일하게 활성화된 인스턴스 싱글톤일 때 적절하겠다. 하지만 이에만 의존할 수 없는 것이, 상기 표시한 코드에서와 같이 비주얼 스몰토크의 많은 transient 싱글톤 클래스가 current 메시지를 사용하기 때문이다. default 이름 또한 오해의 소지가 있다. 어떤 클래스는 싱글톤 인스턴스가 아닌, 실제 디폴트(default) 값을 제공하기 위해 사용한다. 비주얼웍스를 예로 들면, Time default는 당시 시계가 가리키는 시각을 포함하는 Time 객체를 검색한다; Date current에서도 마찬가지다. 하지만 둘다 싱글톤이 아니다. 여기서 까다로운 점은 자연언어가 모호하다는 사실이다: default 와 같은 단어들이 다르게 해석될 수 있다. Fowler (1997)가 주장한 바와 같이 이름 지정은 객체지향 분석에서 까다로운 문제들 중 하나이며 "어떠한 이름도 완벽하지 않다" (p.9).
[디자인 패턴]에서는 instance; 메서드를 단순하게 부를 것을 권한다; Buschmann et al. (1996)는 getInstance를 추천하였다. 아마도 검색된 객체, 즉 defaultNotifier나 currentSourceManager와 같이 객체에 대한 더 많은 정보를 제공하는 이름을 사용해야 할지도 모른다. 이는 Beck (1997)이 Intention Revealing Selector라는 부르는 것을 적용시킨 것이다. 그러나 많은 저자들은 SourceManager currentSourceManager와 같은 식은 불필요하게 길며 오히려 정해진 스몰토크의 협약(convention)이 (예: SourceManager current 또는 Screen default) 더 적절하다고 주장할 것이다.
가장 좋은 충고는 공통된 방식을 사용하여 이름을 정하고, 항상 한 걸음 물러나 자신의 코드를 반영하며, 다른 누군가 자신의 코드를 읽고 이해할 것이라는 사실을 명심하는 것이다.
클래스 메서드만?
클래스가 하나의 인스턴스만 가져야 할 경우 스몰토크 프로그래머는 클래스 메서드처럼 그의 모든 행위를 구현하고 싶을 때가 있다. 클래스 객체는 메시지 전송 및 수신 능력을 가지기 때문에 인스턴스와 인스턴스 메서드를 가지지 않도록 클래스를 설계할 수도 있고 클래스 측면에 전체적인 싱글톤 프로토콜을 구현할 수도 있다 (클래스 메서드와 같이). 사실상 스몰토크/V의 이전 버전에서 (스몰토크 익스프레스도 포함) Compiler 클래스가 구현한 것과 마찬가지로 비주얼 스몰토크의 OLESessionmanager와 OLERegistryInterface 클래스는 위의 방식으로 그 행위를 구현한다.
객체를 완전히 클래스 측면에서 구현하는 것을 피하는 두 가지 이유가 있는데, 하나는 이론적인 이유고 하나는 실용적 이유에서이다. 이론적 이유는 그것이 메타클래스 프로토콜을 자연스럽지 못한 방식으로 확장한다는 것이다. "일반 객체는 실세계를 모델화하는데 사용된다. 메타객체는 이러한 일반 객체를 설명한다." (Rivard, 1997). 클래스는 메타객체ㅡ도메인 객체보다는 스몰토크 환경의 일부ㅡ이어야 한다. 이들의 주요 역할로는 인스턴스 생성, 인스턴스로 접근 (싱글톤에서와 마찬가지로), 그리고 클래스의 모든 인스턴스에 공통되는 리소스 공유가 포함된다. 이러한 역할들은 클래스 메서드에서 구현된다. 인스턴스 메서드와 클래스 메서드의 구분은 행위의 명세로부터 인스턴스의 생성 및 관리를 분리시킨다. 클래스 메서드에 의해 구현된 도메인 특정적 또는 애플리케이션 특정적 행위는 이러한 구분을 흐리게 만든다. 이러한 종류의 행위는 인스턴스 측면에 속한다.
클래스 측면에서의 구현을 피하는 실용적 이유는 확장성과 관련된다: 후에 시스템이 하나 이상의 객체 인스턴스를 필요로 할 경우 설계를 유지하고 확장하기가 힘들 것이다. 시스템이 클래스 메시지를 통해 객체를 처리하는 코드가 많아진 경우, 비싱글톤 (nonSingleton) 설계로 전환하게 되면 모든 코드를 찾아 수정해야 함을 의미한다. 이러한 변경은 인스턴스 메서드에 의해 싱글톤의 행위가 이미 구현되어 있을 때 훨씬 수월할 것이다.
예제 코드
어떤 회사에 자체 제작한 데이터베이스를 사용하는 시스템이 있다고 치자. 구체적으로 말해, 이 시스템은 상업적 관계 또는 객체지향 데이터베이스 제품을 사용하기보다는 일반적 파일접근 루틴을 이용해 하나의 플랫 디스크 파일로부터 데이터를 저장하고 검색한다. 상업적 데이터베이스는 애플리케이션에 관한 파일을 처리하고 잠금을 기록하지만, 우리 애플리케이션은 플랫 디스크 파일을 읽고 쓰기 때문에 고유의 잠금 메커니즘을 제공해야 한다. 따라서 매우 단순한 파일수준의 잠금 구현을 소개하고자 한다.
하나의 쓰레드(thread)가 파일로 기록을 쓰는 동안 다른 하나가 읽기 요청을 발행하는 다중 쓰레드 애플리케이션을 회사에서 사용한다고 가정해보자. 데이터베이스 접근 객체는 쓰기 오퍼레이션의 진해여부를 알려주는 인스턴스 변수를 이용해 파일 수준의 잠금을 통제할 것이다. 읽기 오퍼레이션은 최근 쓰기가 완료될 때까지 기다릴 것이다. 이제 Singleton 패턴의 사용을 필요로 하는 문제가 생긴다: 데이터베이스 접근자는 잠금에 인스턴스 변수를 사용하므로, 우리는 하나의 접근자 객체를 갖길 바라며, 이를 이용해 대기 중인 쓰기가 완료되기 전까지는 파일로부터 읽지 못하도록 막을 수 있길 바란다. (물론 이것은 실제 데이터베이스 관리자의 요구사항 중 일부에 불과하다. 예를 들어 읽기-쓰기 충돌을 방지하기 위해 좀 더 정교한 기록수준의 잠금 메커니즘이 필요할 수도 있다. 하지만 여기서 나타내고자 하는 중요한 목표는 Singleton 패턴을 사용하는 애플리케이션을 단순히 보여주는 것이다.)
여기 DatabaseAccessor를 싱글톤으로 구현하는 주요 정의 3가지가 있다:
Object subclass: #DatabaseAccessor
instanceVariableNames: 'lock'
classVariableNames: 'Instance'
poolDictionaries: ''
DatabaseAccessor class>>singleton
Instance isNil
ifTrue: [Instance := self basicNew initialize].
^Instance
DatabaseAccessor class>>new
^self error: 'DatabaseAccessor has only one instance. ',
'To retrieve it, send "DatabaseAccessor singleton".'
다음으로는 DatabaseAccessor 인스턴스 행위를 정의한다:
DatabaseAccessor>>initialize
lock := false.
"Open the file"
...
DatabaseAccessor>>write: aDatabaseRecord
"Set the lock and fork the 'real' write method."
lock := true.
[self writePrim: aDatabaseRecord] fork
DatabaseAccessor>>writePrim: aDatabaseRecord
"Write the record in aDatabaseRecord to the file."
...
"Now that the write is complete, unlock:"
lock := false.
DatabaseAccessor>>read: aKey
"Return the DatabaseRecord keyed by aKey."
| Record |
"Don't read while a write is in progress."
[lock] whileTrue: [Processor yield].
"Now, read the record:"
record := DatabaseRecord new.
...
^record
클라이언트 애플리케이션이 기록을 읽고자 할 경우 다음을 코딩한다:
DatabaseAccessor singleton read: aKey
이와 비슷하게, 기록을 쓰는 경우에도 클라이언트는 DatabaseAccessor default를 전송하여 검색된 싱글톤 데이터베이스 접근 객체에게 write: 메시지를 보낸다.
알려진 스몰토크 사용예
논의에서 이미 기본 스몰토크 라이브러리에 사용되는 싱글톤의 예제를 여러 개 소개하였다. 여기서는 몇 가지 싱글톤 구현 범주 내에서 예제를 열거하고자 한다.
영속 싱글톤
persistent Singletons
비주얼 스몰토크 이미지에 사용되는 영속 싱글톤의 예제가 몇 가지 있다. NotificationManager의 Notifer 인스턴스에 대해서는 이미 언급한 바 있다. 싱글톤 ClipboardManager를 참조하는 global인 Clipboard는 클라이언트에게 운영시스템의 클립보드 리소스로의 접근성을 제공해준다. Global Processor는 PRocessScheduler에 대한 싱글톤 인스턴스이다; 이는 Process 객체들과 다중처리 업무를 관리한다. IBM 스몰토크는 후자의 예제와 정확히 동일한 배열을 가진다; global Processor는 싱글톤 ProcessScheduler를 참조한다.
일시 싱글톤
Transient Singletone
비주얼웍스에서 SourceFileManager의 싱글톤 인스턴스는 코드 파일을 추적한다 (소스와 변경 파일). 비주얼 스몰토크에는 SourceManager current로 접근되는 유사한 이름의 클래스 SourceManager가 있다.
비주얼 스몰토크에서 싱글톤 SessionModel은 최근 스몰토크 세션에 관련된 정보를 추적한다. 이는 Sessionmodel current라는 메시지를 이용해 접근된다.
비주얼웍스는 Screen 클래스를 구현하는데, 이의 유일한 인스턴스는 최근 사용한 디스플레이를 나타낸다 (Screen default). 이 클래스는 싱글톤으로 정의되는데, 그 이유는 비주얼웍스가 다수의 모니터를 지원하지 않기 때문이다. 비주얼 스몰토크에도 Screen이라 불리는 클래스가 있는데, 이 클래스의 유일한 인스턴스는 전역변수 Display에 저장된다.
단일 활성 인스턴스 싱글톤
Single-Actgive Instance Singletone
비주얼웍스는 개발자가 원하는 수 만큼의 Project 를 관리할 수 있지만 한 번에 활성화되어 있는 프로젝트는 하나뿐이다 (Project current). 최근 실행한 프로젝트는 최근 변경 집합과 (ChangeSet current) 최근 표시된 윈도우 집합을 (ControlManager의 인스턴스인 global ScheduledControllers에 유지되는) 적는다. 이 모든 클래스는 다수의 인스턴스를 가지지만 한 번에 하나의 인스턴스만 "살아 있다."
하위계층구조에서 클래스별 싱글톤
비주얼 스몰토크에서 DynamicLinkLibrary는 외부 동적 링크 라이브러리 (DLL) 내의 코드와 스몰토크 간 의사소통을 구현하는 추상 클래스이다. 특정 DLL 파일은 고유의 구체적 서브클래스를 통해서 접근된다. 추상 클래스는 각 서브클래스에 대한 싱글톤 인스턴스를 저장하기 위해 클래스 인스턴스 변수와 (current) 함께 동일한 이름의 해당 접근자 메서드를 정의한다. 따라서 current를 어떤 서브클래스로 전송하면 (예: GDIDLL current) 그 클래스의 싱글톤 인스턴스를 검색한다.