SqueakByExample:5.7
공유 변수
이제 다섯 가지 규칙이 잘 적용되지 않는 스몰토크의 다른부분을 살펴보겠습니다 : 공유변수Shared variables 입니다.
스몰토크에는 3가지 종류의 공유변수가 있습니다: (1) 전역 공유 변수 (2) 인스턴스와 클래스 사이에서 공유되는 변수(클래스 변수) 그리고 (3)클래스의 그룹(Category) 사이에서 공유된 변수(pool 변수)들. 이 세가지의 공유변수들은 사용자에게, 이 변수들은 공유된 변수라는 것을 알려주기 위해 대문자로 시작됩니다.
전역 변수
스퀵에서, 모든 전역 변수는 클래스 SystemDictionary 의 인스턴스로서 실행되며, Smalltalk 라고 불리는 네임스페이스에 저장됩니다. 스몰토크의 전역 변수는 어디서나 접근이 가능합니다. 모든 클래스는 전역 변수 같은 형식의 이름을 가지게 됩니다.[1] 게다가, 일부 이름은 특별하거나 일반적으로 유용한 객체를 이름지을 때 사용합니다.
Transcript 변수는 스크롤 창에 데이터를 기록하는 스트림인 TranscriptStream 인스턴스의 변수명 입니다. 다음 코드는 Transcript에 일부 정보를 표시하고 표시 커서를 다음 줄로 이동시킵니다.
Transcript show: 'Squeak is fun and powerful' ; cr
do it 을 클릭하기 전에, Tool 플랩 에서 Transcript 를 드래그하여 트랜스크립트를 열어야 합니다.
HINT Transcript 에 데이터를 기록하는 작업은 Transcript 창이 열렸을 때 더 느려집니다. 따라서, Transcript 에 데이터를 기록하는 동작이 느리다고 느껴진다면, Transcript 창을 닫는 것을 고려해 보십시오.
다른 유용한 전역 변수들
- Smalltalk는 스몰토크 자신을 포함해서, 모든 전역 변수를 정의하는 SystemDictionary 의 인스턴스 입니다. SystemDictionary 라는 dictionary의 핵심은 스몰토크 코드에서 전역 오브젝트를 이름짓는 심볼입니다. 아래 예를 보겠습니다.
Smalltalk at: #Boolean ⇒ Boolean
Smalltalk 자체가 전역 변수이므로,Smalltalk at: #Smalltalk ⇒ a SystemDictionary(lots of globals)}
그리고(Smalltalk at: #Smalltalk) == Smalltalk ⇒ true
- Sensor는 EventSensor의 인스턴스이며, Squeak으로 들어가는 입력들을 처리합니다. 예를 들면, Sensor keyborad는 키보드에 다음 문자 입력에 응답하며, Sensor mousePoint 는 Point는 현재 마우스 위치를 알려주는 반면에, 만약 왼쪽 쉬프트 키가 눌려 있는 상태인 경우 Sensor leftShiftDown 은 true로 응답됩니다.
- World 는 화면을 표시하는 PasteUpMorph 클래스의 인스턴스입니다. World bounds 는 전체 화면 공간을 정의하는 직사각형의 정보를 반환하며, 화면의 모든 모프는 World 의 하위모프가 됩니다.
- ActiveHand는 HandMorph의 현재 인스턴스이며, 커서의 그래픽 표현입니다. ActiveHand의 서브모프는 마우스로 드래그한 모든 것을 잡습니다.
- Undeclared 는 또다른 dictionary입니다 - Undeclared dictionary에는 모든 미리 선언되지 않은 변수들이 포함됩니다. 만약 당신이 선언하지 않은 함수를 참조하는 메서드를 작성하게 된다면, 시스템 브라우저는 대개의 경우 선언되지 않은 함수를 선언하도록 알려줍니다. 하지만, 만약 나중에 해당되는 선언문을 삭제한다면, 코드는 선언문이 지워졌기 때문에, dictionary에는 있지만 실제로는 선언되지 않은 변수들을 참조하는 상황이 됩니다. Undeclared dictionary를 살펴보면 가끔 이상한 동작을 하는경우에 대해 도움을 얻을 수 있는 경우도 있습니다.
- SystemOrganization 은 SystemOrganizer 클래스의 인스턴스입니다: SystemOrganization은 패키지별로 클래스들의 그룹을 기록합니다. 좀 더 정확하게 말하자면, 이것은 클래스들의 이름을 그룹으로 만들어 분류(categorizes)합니다. 그 덕분에 다음과 같은 코드로 결과를 얻을 수 있습니다[2].
SystemOrganization categoryOfElement: #Magnitude ⇒ #'Kernel-Numbers'
현재 진행하실 연습에서는 전역 변수의 사용을 엄격히 제한해주세요. 인스턴스 변수 또는 클래스 변수를 사용하거나, 전역 변수들에 접근하기 위해서 클래스 메서드를 제공하는 방법이 더 좋은 방법입니다. 정말로, 스퀵을 현재 상태로 바닥부터 구현한다면, 클래스가 아닌 대부분의 전역번수들은 아마도 Singleton 으로 대체되었을 겁니다.
보통 전역 요소를 정의할때는 머릿 글자를 대문자로 하였지만 아직 선언하지 않은 식별자undeclared identifier를 마우스로 영역선택하고 do it을 실행하면 됩니다. 파서parser는 그 다음, 사용자를 위해 선언할 전역 변수를 제공할 것입니다. 만약 프로그램문장 으로 전역 변수를 정의하기 원한다면, Smalltalk at: #AGlobalName put: nil 을 실행하면 됩니다. 이렇게 만들어진 변수를 제거하려면, Smalltalk removeKey: #AGlobalName 를 실행합니다.
클래스 변수
때로는 클래스 인스턴스와 클래스 자신 양쪽에 무슨 데이터든 공유할 필요가 있습니다. 이런 작업에는 클래스 변수를 사용하면 됩니다. 클래스 변수라는 용어는 변수의 수명이 클래스의 수명과 같음을 나타냅니다. 하지만 이런 의미로는 좀 모자라는것이, 클래스 변수는 그림 5.5에 보이는 것처럼 클래스 자체 뿐만 아니라 클래스의 모든 인스턴스들 사이에서도 그 내용이 공유되기 때문입니다. 실제로, 클래스 변수 보다 좀 더 나은 이름을 쓴다면 '공유 변수' 가 되겠죠. 왜냐하면, 이 이름이 좀 더 명확하게 클래스변수의 역할을 설명해주기 때문이며 또한, 변수들이 수정되는 경우, 사용상 위험성을 사용자에게 경고하기 때문입니다.
그림 5.5에서, 우리는 rgb와 cachedDepth가 Color 인스턴스의 인스턴스 변수인 것을 알 수 있으며, Color 인스턴스만 접근이 가능한 존재라는걸 알 수 있습니다[3]. 또한 superclass, subclass, methodDic 과 다른 것들이 color 클래스의 클래스 인스턴스 변수임을 알 수 있습니다. 말하자면, 이런 클래스 인스턴스 변수들은 오직 color 클래스에서만 접근이 가능다는 내용입니다.
그러나 약간 새로운 점도 있습니다: ColorNames 와 CachedColormaps 는 Color 를 위해 정의된 class 변수입니다. 이 변수들의 이름중 맨 앞글자가 대문자인걸 보면 이것들이 공유변수라는걸 암시합니다. 사실, Color 의 모든 인스턴스는 이 공유변수들에 접근가능할 뿐만 아니라, Color 클래스 자체와 그 클래스의 모든 서브클래스도 공유변수에 대한 접근이 가능합니다. 인스턴스 메서드와 클래스 메서드 양쪽다, 이 공유변수에 접근이 가능합니다.
클래스 변수는 클래스 정의 템플릿에 선언됩니다. 예를 들어, Color 클래스에서는 색상을 빨리 만들기 위한 수많은 클래스 변수를 정의하고 있으며, 내용은 아래에서 볼 수 있습니다. (클래스 5.20)
클래스 5.20: Color와 Color의 클래스 변수
Object subclass: #Color
instanceVariableNames: 'rgb cachedDepth cachedBitPattern'
classVariableNames: 'Black Blue BlueShift Brown CachedColormaps ColorChart
ColorNames ComponentMask ComponentMax Cyan DarkGray Gray
GrayToIndexMap Green GreenShift HalfComponentMask HighLightBitmaps
IndexedColors LightBlue LightBrown LightCyan LightGray LightGreen
LightMagenta LightOrange LightRed LightYellow Magenta MaskingMap Orange
PaleBlue PaleBuff PaleGreen PaleMagenta PaleOrange PalePeach PaleRed
PaleTan PaleYellow PureBlue PureCyan PureGreen PureMagenta PureRed
PureYellow RandomStream Red RedShift TranslucentPatterns Transparent
VeryDarkGray VeryLightGray VeryPaleRed VeryVeryDarkGray
VeryVeryLightGray White Yellow'
poolDictionaries: ''
category: 'Graphics--Primitives'
클래스 변수인 ColorNames 는 자주쓰이는 색상의 이름을 포함하고 있는 배열입니다. 이 배열은 color의 모든 인스턴스에서 공유되며, 배열의 서브클래스는 TranslucentColor 입니다. 이 배열은 모든 인스턴스와 클래스 메서드들로 부터 접근 가능합니다.
클래스 변수인 ColorNames 는 일단 Color클래스>>initializeNames 에서 초기화되지만, Color 클래스의 인스턴스들이 접근(사용)합니다. 메서드 Color>>name 은 색상의 이름을 찾기 위해 ColorNames 변수를 사용합니다. 모든 색상의 이름이 있는건 아니라서, 인스턴스 변수에 대부분 색상의 이름을 추가하는건 좋지 않습니다.
클래스 초기화
클래스 변수를 보면 이런 의문이 생깁니다: 어떻게 초기화하지? 이런 질문에 대한 해결책중 하나는 게으른초기화(lazy initialization) 입니다. 아직 초기화가 되지 않은 변수에 접근할 때 초기화를 진행하는 접근자 메서드를 도입하면 가능합니다. 하지만 게으른 초기화는 언제나 접근자를 사용해야 하며 클래스 변수를 직접 사용하면 안된다는 의미가 됩니다. 더욱이 접근자 전송과 초기화 테스트의 부담을 보태게 되죠. 또한 이 방법은 클래스 변수를 사용하는 이유을 없애버립니다, 왜냐하면 사실상 클래스변수가 더 이상 공유 되지 않기 때문입니다.
메서드 5.21: Color class>>colorNames
Color class>>colorNames
ColorNames ifNil: [self initializeNames].
↑ ColorNames
게으른초기화 외의 다른방법은, 클래스 메서드 초기화를 재지정override 하는 것입니다.
메서드 5.22: Color class>>initialize
Color class>>initialize
...
self initializeNames
만약 초기화를 재지정하는 방법을 쓴다면 initialize 메서드를 정의한다음 실행해야 할 필요가 있다는걸 알고있어야 하는데 Color initialize 를 실행하는것처럼 하면 됩니다. 비록 클래스의 코드가 메모리에 로드될때 (시스템브라우저의)클래스측면에서 볼 수 있는 초기화 메서드들이 자동으로 실행되지만, 시스템 브라우저 에서 처음 입력하고 편집 및 컴파일했을때도 자동으로 실행되는건 아닙니다.
Pool 변수
Pool 변수는 상속 관계가 없을 수 있는 수많은 클래스사이에 공유되는 변수입니다. Pool 변수는 원래 pool dictionary에 저장됩니다; 하지만 지금은 단독 클래스(SharedPool의 서브클래스)의 클래스 변수처럼 정의해야 합니다. 우리는 이 변수를 사용하지 않기를 권합니다. 왜냐하면 매우 특히한 상황에서만 Pool변수가 필요하기 때문이죠. 여기서 Pool변수를 설명하는 이유는 코드를 분석할때 도움이 되도록 하기 위함입니다.
pool 변수에 접근하는 클래스는 접근을 원하는 클래스 정의에서 반드시 pool을 언급해야 합니다. 예를 들면, Text 클래스는 자신이 pool dictionary인 TextContansts를 사용하고 있다는걸 선언하며, TextContansts 라는 Pool변수는 CR과 LF와 같은 모든 문자 상수를 포함합니다. TextContansts dictionary는, 예를 들면 캐리지 리턴carriage return 문자값을 가진 Character cr 에 묶인 key #CR 등을 가지고 있습니다[4].
클래스 5.23: Text 클래스에 있는 Pool dictionary
ArrayedCollection subclass: #Text
instanceVariableNames: 'string runs'
classVariableNames: ''
poolDictionaries: ’TextConstants’
category: 'Collections--Text'
이러한 poolDictionary의 선언은, 클래스 5.23에 있는 Text 클래스의 메서드가 메서드 내용의 dictionary 키로 직접directly 접근하는 것을 허용합니다. 예를 들어, 다음과같은 메서드를 작성할 수 있다는 얘기죠.
메서드 5.24: Text»testCR
Text>>testCR
↑ CR == Character cr
다시 말하지만, 우리는 당신이 pool 변수와 pool dictionary 사용을 피하실 것을 권장하는 바입니다.