Smalltalk80LanguageImplementationKor:Chapter 16

From 흡혈양파의 번역工房
Jump to: navigation, search
제 16 장 클래스에 대한 프로토콜

클래스에 대한 프로토콜

Object

    Magnitude
        Character
        Date
        Time

        Number
            Float
            Fraction
            Integer
                LargeNegativeInteger
                LargePositiveInteger
                SmallInteger

        LookupKey
            Association

    Link

        Process

    Collection

        SequenceableCollection
            LinkedList

                Semaphore

            ArrayedCollection
                Array

                Bitmap
                    DisplayBitmap

                RunArray
                String
                    Symbol
                Text
                ByteArray

            Interval
            OrderedCollection
                SortedCollection
        Bag
        MappedCollection
        Set
            Dictionary
                IdentifyDictionary

    Stream
        PositionableStream
            ReadStream
            WriteStream
                ReadWriteStream
                    ExternalStream
                        FileStream

        Random

    File
    FileDirectory
    FilePage

    UndefinedObject
    Boolean
        False
        True

    ProcessorScheduler
    Delay
    SharedQueue

    Behavior***
        ClassDescription***
            Class***
            MetaClass***

    Point
    Rectangle
    BitBit
        CharacterScanner

        Pen

    DisplayObject
        DisplayMedium
            Form
                Cursor
                DisplayScreen
        InfiniteForm
        OpaqueForm
        Path
            Arc
                Circle
            Curve
            Line
            LinearFit
            Spline


지금까지 Smalltalk-80 시스템의 기본 구성요소를 설명하는 클래스 대부분에 대한 프로토콜을 소개하였다. 한 가지 눈에 띄는 예외는 클래스 자체에 대한 프로토콜이다. 네 가지 클래스, 즉 Behavior, ClassDescription, Metaclass, Class는 새로운 클래스를 설명하는 데 필요한 기능을 제공하기 위해 상호작용한다. 새로운 클래스를 생성하기 위해서는 메서드의 컴파일이 필요하고, 인스턴스 변수, 클래스 변수, pool 변수, 클래스 자체에 대한 이름을 명시해야 한다.


제 3, 4, 5장에서는 이러한 클래스들이 표현하는 기본 개념을 소개하였다. 논의의 내용을 요약하기 위해 Smalltalk-80 프로그래머는 또 다른 클래스의 서브클래스를 생성함으로써 새로운 클래스를 명시한다. 가령, Collection 클래스는 Object의 서브클래스이고, Array 클래스는 ArrayedCollection(이것의 슈퍼클래스 사슬은 Object로 끝이 난다)의 서브클래스다.

1. 모든 클래스는 결국 Object의 서브클래스에 해당하지만 슈퍼클래스가 없는 Object 자체는 제외된다. 특히 Class는 ClassDescription의 서브클래스인데, ClassDescription은 Object의 서브클래스 Behavior의 서브클래스에 해당한다.


시스템에는 스스로(클래스)의 인스턴스를 생성할 수 있는 객체와 인스턴스를 생성할 수 없는 객체, 두 종류가 있다.

2. 모든 객체는 클래스의 인스턴스다.


모든 클래스는 그 자체가 어떤 클래스의 인스턴스다. 우리는 클래스의 클래스를 메타클래스라고 부른다.

3. 모든 클래스는 메타클래스의 인스턴스다.


메타클래스는 여느 클래스처럼 클래스명으로 참조되는 것이 아니다. 대신 메타클래스는 메타클래스의 인스턴스로 단항 메시지 class를 전송하는 메시지 표현식에 의해 참조된다. 예를 들어, Collection의 메타클래스는 Collection class로서 참조되고, Class의 메타클래스는 Class class라고 부른다.


Smalltalk-80 시스템에서 메타클래스는 새로운 클래스가 생성될 때마다 자동으로 생성된다. 메타클래스에는 하나의 인스턴스만 존재한다. 클래스 기술에서 "class methods"(클래스 메서드)로 분류되는 메시지는 클래스의 메타클래스에서 찾을 수 있다. 이는 메서드가 발견되는 방식에 따라 객체로 메시지가 전송되면 객체의 클래스에서 그에 상응하는 메서드의 검색이 시작된다. 가령 Dictionary로 메시지가 전송되면 Dictionary의 메타클래스에서 검색이 시작된다. 메타클래스에서 메서드를 찾을 수 없으면 검색은 메타클래스의 슈퍼클래스로 진행된다. 이번 경우 슈퍼클래스는 Set class, 즉 Dictionary의 슈퍼클래스에 대한 메타클래스가 된다. 필요 시 검색은 Object class까지 이어지는 슈퍼클래스 사슬을 따라간다.


본 장에 실린 그림에서 실선으로 된 화살표는 모두 서브클래스 관계를 나타내고, 파선으로 된 화살표는 인스턴스 관계를 나타낸다. A--->B는 A가 B의 인스턴스임을 나타낸다. 회색 실선은 클래스 계층구조를 나타내고, 검정색 실선은 메타클래스 계층구조를 나타낸다.

그림 16-1


그림 16.1에 표시된 바와 같이 모든 객체의 슈퍼클래스 사슬은 Object에서 끝나기 때문에 Object는 슈퍼클래스를 갖고 있지 않고 Object의 메타클래스의 슈퍼클래스는 병렬적 계층구조를 유지하는 규칙으로 결정되지 않는다. 이 시점에서는 Class가 발견되지 않는다. Object 클래스의 슈퍼클래스는 Class이다.

4. 모든 메타클래스는 (결국) Class의 서브클래스다 (그림 16.2).

그림 16-2


메타클래스는 객체이므로 클래스의 인스턴스여야 한다. 모든 메타클래스는 Metaclass의 인스턴스다. Metaclass 자체도 메타클래스의 인스턴스다. 이것이 시스템에 존재하는 순환성(circularity)으로, Metaclass의 메타클래스는 Metaclass의 인스턴스여야 한다.

5. 모든 메타클래스는 Metaclass의 인스턴스다 (그림 16.3).

그림 16-3


그림 16.4는 Class, ClassDescription, Behavior, Object와 그들 각각의 메타클래스 간 관계를 보여준다. 클래스 계층구조는 Object로 이어지는 사슬을 따르고, 메타클래스 계층구조는 Object class를 통해 Class로, 이윽고 Object로 이어지는 사슬을 따른다. Object의 메서드는 모든 객체에 공통된 행위를 지원하는 반면 Class와 Metaclass의 메서드는 모든 클래스에 공통된 행위를 지원한다.


6. Class와 그 슈퍼클래스의 메서드는 클래스에 해당하는 객체에 공통된 행위를 지원한다.
7. Metaclass의 인스턴스의 메서드는 특정 클래스에 특정적인 행위를 추가한다.


클래스 계층구조와 메타클래스 계층구조 간 통신을 그림 16.5에 실었는데, 마지막 두 그림의 숫자 계층구조와 행위 계층구조의 일부를 결합하여 표시하였다.

그림 16-4

그림 16-5


Behavior 클래스

Behavior 클래스는 인스턴스가 있는 객체에 필요한 최소 상태를 정의한다. Behavior는 특히 Smalltalk-80 인터프리터가 사용하는 상태를 정의한다. 또 컴파일러에게 기본 인터페이스를 제공한다. Behavior가 설명하는 상태로는 클래스 계층구조 링크, 메서드 사전, 숫자와 그 변수의 표현과 관련된 인스턴스의 설명이 있다.


Behavior 클래스에 대한 메시지 프로토콜은 네 가지 범주로 설명할 것인데, 생성, 접근, 검사, 열거가 해당한다. 이러한 범주와 그 하위범주는 아래 표시된 바와 같이 Smalltalk-80 시스템에서 클래스의 기능에 관해 생각하기 위한 모델을 제공한다.


모든 클래스에 대한 프로토콜의 개요

creating(생성하기)

  • 메서드 사전 생성하기
  • 인스턴스 생성하기
  • 클래스 계층구조 생성하기

accessing(접근하기)

  • 메서드 사전의 내용 접근하기
  • 인스턴스와 변수로 접근하기: 인스턴스, 클래스, pool
  • 클래스 계층구조 접근하기

testing(검사하기)

  • 메서드 사전의 내용 검사하기
  • 인스턴스의 형태 검사하기
  • 클래스 계층구조 검사하기

enumerating(열거하기)

  • 서브클래스와 인스턴스 열거하기


❏ Behavior의 생성 프로토콜


클래스 설명에 있는 메서드는 "method dictionary"(메서드 사전)라고 부르는 사전에 저장된다. 때로는 메시지 사전이라고 부르기도 한다. 이 사전에 있는 키는 메시지 선택자이고, 값은 컴파일된 메서드의 형태다 (CompiledMethod의 인스턴스). 메서드 사전을 생성하기 위한 프로토콜은 선택자와 컴파일된 메서드 간 연관을 추가할 뿐만 아니라 메서드의 컴파일도 지원한다. 메서드의 컴파일 버전과 비컴파일 (소스) 버전으로의 접근도 지원한다.

메서드 사전 생성하기(creating method dictionary)
methodDictionary: aDictionary 인자 aDictionary를 수신자의 메서드 사전으로서 저장한다.
addSelector: selector withMethod: compiledMethod 메시지 선택자 selector와 그에 상응하는 컴파일된 메서드 compiledMethod를 수신자의 메서드 사전으로 추가한다.
removeSelector: selector 수신자의 메서드 사전에서 인자 selector(메시지 선택자를 나타내는 Symbol)를 제거한다. 선택자가 메서드 사전에 있지 않으면 오류를 보고한다.
compile: code 인자 code는 String 또는 String으로 변환하는 객체이거나, String 또는 String으로 변환하는 객체로 접근하는 PositionableStream이다. 수신자의 변수에 대한 컨텍스트에서 코드를 소스 코드로서 컴파일한다. 코드를 컴파일할 수 없다면 오류를 보고한다.
compile: code notifying: requestor 인자 code를 컴파일하고 결과를 수신자의 메서드 사전에 입력한다. 오류가 발생하면 적절한 메시지를 인자 requestor로 전송한다.
recompile: selector 메시지 선택자 selector와 연관된 메서드를 컴파일한다.
decompile: selector 인자 selector와 연관된 컴파일된 코드를 검색하고 역컴파일한다. 결과가 되는 소스 코드를 String으로서 응답한다. 선택자가 메서드 사전에 없다면 오류를 보고한다.
compileAll 수신자의 메서드 사전 내 모든 메서드를 컴파일한다.
compileAllSubclasses 수신자의 서브클래스의 메서드 사전 내 모든 메서드를 컴파일한다.
Behavior 인스턴스 프로토콜


클래스의 인스턴스는 new 또는 new: 메시지를 전송하여 생성된다. 이 두 메시지는 특별한 초기화 행위를 제공하기 위해 메타클래스의 메서드 사전에서 오버라이드할 수 있다. 어떠한 특수 초기화든 그 목적은 적절한 인스턴스에 해당하는 변수와 함께 인스턴스가 생성되도록 보장하기 위함이다. 이와 관련된 개념은 앞의 여러 장에서 설명하였다. 예를 들어, 12장에 실린 Random 클래스의 정의를 살펴보면, Random 클래스의 메서드 사전은 (클래스 메서드) 새로운 인스턴스에 setSeed 메시지가 전송되는 new의 구현을 포함하고 있는데, 이러한 초기화는 난수 생성 알고리즘이 적절한 유형의 숫자에 해당하는 변수를 참조하도록 확보한다.


클래스가 new에 대한 메서드를 오버라이드 한 후에 그 서브클래스 중 하나가 똑같은 일을 수행하여 슈퍼클래스의 변경으로 인해 생성된 행위를 피한다고 가정해보자. 첫 번째 클래스에 대한 메서드는 아래와 같을 것인데

new
    super new setVariables


여기서 클래스의 인스턴스에 대한 프로토콜로 setVariables 메시지가 제공된다. 의사변수 super로 new 메시지를 전송함으로써 Behavior 클래스에 명시된 바와 같이 인스턴스를 생성하기 위한 메서드가 평가되고, 그 결과물인 새로운 인스턴스에 setVariables 메시지가 전송된다. 서브클래스에서는 super new 메시지를 이용하는 것이 불가능한데, 이를 이용할 경우 첫 번째 클래스의 메서드, 정확히 말하자면 피해야 하는 메서드를 호출할 것이기 때문이다. 인스턴스를 생성하기 위한 기본 메서드를 Behavior에서 얻기 위해서는 서브클래스가 self basicNew 표현식을 사용해야만 한다. basicNew 메시지는 어떠한 서브클래스에서도 재구현되어선 안 되는 프리미티브 인스턴스 생성 메시지다. Behavior에서는 new와 basicNew가 동일하다. 이와 비슷하게 가변 길이의 객체를 생성하기 위한 메시지 쌍, new: 와 basicNew: 또한 Behavior 클래스의 프로토콜에 제공된다. (이러한 이중 메시지 기법은 Object 클래스에서 at: 과 at:put: 과 같은 메시지로 접근 시 사용되기도 한다는 사실을 주목한다.)

인스턴스 생성(instance creation)
new 색인된 변수가 없는 수신자의 인스턴스를 응답한다. 수신자가 색인 가능할 경우 new: 0 메시지를 수신자에게 전송한다.
basicNew 해당 메서드는 서브클래스에서 오버라이드되어선 안 된다는 점만 제외하면 new와 동일하다.
new: anInteger 색인된 변수의 anInteger 수와 함께 수신자의 인스턴스를 응답한다. 수신자가 색인 가능하지 않으면 오류를 보고한다.
basicNew: anInteger 해당 메서드는 서브클래스에서 오버라이드되어선 안 된다는 점만 제외하면 basicNew와 동일하다.
Behavior 인스턴스 프로토콜


클래스를 생성하기 위한 프로토콜은 클래스를 시스템에서 클래스의 계층구조 내부에 위치시키기 위한 메시지를 포함한다. 이러한 계층구조는 선형적이기 때문에 슈퍼클래스를 설정하고 서브클래스를 추가하거나 제거만 필요하다.

클래스 계층구조 생성하기(creating a class hierarchy)
superclass: aClass 수신자의 슈퍼클래스가 인자 aClass가 되도록 설정한다.
addSubclass: aClass 인자 aClass를 수신자의 서브클래스로 만든다.
removeSubclass: aClass 수신자의 서브클래스에서 인자 aClass를 제거한다.
Behavior 인스턴스 프로토콜


Behavior에 대한 생성 프로토콜은 새로운 클래스 설명을 생성하기 위한 표현식을 작성하는 것이 가능하게 만들지만 일반적으로는 Smalltalk-80 언어가 내포되어 있는 그래픽 환경을 활용하고, 클래스의 다양한 부분에 관한 정보를 명시하도록 그래픽으로 표현되는 형태를 사용자가 채워넣는 인터페이스를 제공하는 접근법을 사용한다.



❏ Behavior의 접근 프로토콜


메서드 사전의 내용으로 접근하는 메시지는 부분적으로 명시된 클래스의 메서드 사전 내 선택자들, 그리고 클래스와 그 슈퍼클래스 각각의 메서드 사전 내 선택자들을 구별한다.

메서드 사전 접근하기(accessing the method dictionary)
selectors 수신자의 로컬 메서드 사전에 명시된 모든 메시지 선택자로 구성된 Set을 응답한다.
allSelectors 수신자의 인스턴스가 이해할 수 있는 모든 메시지 선택자로 구성된 Set을 응답한다. 이는 수신자의 메서드 사전 내 모든 메시지 선택자와 수신자의 각 슈퍼클래스의 사전에 있는 모든 메시지 선택자를 포함한다.
compiledMethodAt: selector 수신자의 로컬 메서드 사전에서 메시지 선택자인 인자 selector와 연관된 컴파일된 메서드를 응답한다. 선택자를 찾을 수 없으면 오류를 보고한다.
sourceCodeAt: selector 수신자의 로컬 메서드 사전 내 메시지 선택자인 인자 selector와 연관된 소스 코드에 해당하는 String을 응답한다. 선택자를 찾을 수 없으면 오류를 보고한다.
sourceMethodAt: selector 수신자의 로컬 메서드 사전 내 메시지 선택자인 인자 selector와 연관된 소스 코드에 해당하는 Text를 응답한다. 해당 Text는 메서드의 메시지 패턴 부분을 볼드체로 강조하여 제공한다.
Behavior 인스턴스 프로토콜


인스턴스는 명명된 인스턴스 변수, 색인된 인스턴스 변수, 클래스 변수, pool 변수의 사전을 가질 수 있다. 다시 말하지만 접근 프로토콜에서는 로컬로 명시된 변수와 슈퍼클래스로부터 상속된 변수가 구분되어 있다.

인스턴스와 변수 접근하기(accessing instances and variables)
selectors 수신자의 로컬 메서드 사전에 명시된 모든 메시지 선택자로 된 Set를 응답한다.
allInstances 수신자의 모든 직접적 인스턴스로 된 Set을 응답한다.
someInstance 수신자의 기존 인스턴스를 응답한다.
instanceCount 현재 존재하는 수신자의 인스턴스 개수를 응답한다.
instVarNames 수신자에 명시된 인스턴스 변수의 이름으로 이루어진 Array를 응답한다.
subclassInstVarNames 수신자의 서브클래스에 명시된 인스턴스 변수의 이름으로 구성된 Set을 응답한다.
allInstVarNames 수신자와 그 모든 슈퍼클래스에 명시된 수신자의 인스턴스 변수의 이름으로 이루어진 Array를 응답한다. Array는 Smalltalk-80 인터프리터가 변수를 저장하고 접근하는 순서로 정렬된다.
classVarNames 수신자 내에 로컬로 명시된 클래스 변수의 이름으로 구성된 Set을 응답한다.
allClassVarNames 수신자의 클래스 변수와 수신자의 슈퍼클래스의 클래스 변수의 이름으로 구성된 Set을 응답한다.
sharedPools 수신자 내에 로컬로 명시된 pools의 (dictionaries) 이름으로 구성된 Set을 응답한다.
allSharedPools 수신자의 그 슈퍼클래스에 각각 명시된 pools의 (dictionaries) 이름으로 구성된 Set을 응답한다.
Behavior 인스턴스 프로토콜


따라서, 아래를 예로 들 수 있다.

표현식 결과
OrderedCollection instVarNames ('firstIndex' 'lastIndex')
OrderedCollection subclassInstVarNames Set ('sortBlock')
sortedCollection allInstVarNames ('firstIndex' 'lastIndex' 'sortBlock')
String classVarNames Set (StringBlter)
String allClassVarNames Set (StringBlter DependentsFields ErrorRecursion)
Text sharedPools 하나의 요소 TextConstants를 포함하는 Set로, Dictionary에 해당


접근 메시지들은 클래스의 서브클래스와 슈퍼클래스의 컬렉션을 얻기 위한 메시지를 포함한다. 이러한 메시지는 클래스의 직접적 슈퍼클래스와 서브클래스, 그리고 클래스의 슈퍼클래스 사슬 내 모든 클래스를 구별한다.

클래스 계층구조 접근하기(accessing class hierarchy)
subclasses 수신자의 직접적 서브클래스를 포함하는 Set을 응답한다.
allSubclasses 수신자의 서브클래스와 수신자 자식의 서브클래스로 구성된 Set을 응답한다.
withAllSubclasses 수신자, 수신자의 서브클래스, 수신자 자식의 서브클래스로 구성된 Set을 응답한다.
superclass 수신자의 직접적 슈퍼클래스를 응답한다.
allSuperclasses 수신자의 슈퍼클래스와 수신자 조상의 슈퍼클래스로 이루어진 OrderedCollection을 응답한다. 첫 번재 요소는 수신자의 직접적 슈퍼클래스, 그 다음은 그것의 슈퍼클래스 등으로 이어지고, 마지막 요소는 항상 Object가 된다.
Behavior 인스턴스 프로토콜


따라서 예를 들자면 다음과 같다.

표현식 결과
String superclass ArrayedCollection
ArrayedCollection subclasses Set (Array ByteArray RunArray Bitmap String Text)
ArrayedCollection allSubclasses Set (Array ByteArray RunArray Bitmap String Text DisplayBitmap Symbol CompiledMethod)
ArrayedCollection withAllSubclasses Set (ArrayedCollection Array ByteArray RunArray Bitmap String Text DisplayBitmap Symbol CompiledMethod)
ArrayedCollection allSuperclasses OrderedCollection (SequenceableCollection Collection Object)
ArrayedCollection class allSuperclasses OrderedCollection(SequenceableCollection class Collection class Object class Class ClassDescription Behavior Object)


❏ Behavior의 검사 프로토콜


검사 프로토콜은 클래스의 구조체와 그 인스턴스의 형태에 관한 정보를 알아내는 데 필요한 메시지를 제공한다. 클래스의 구조체는 클래스와 다른 클래스와의 관계, 메시지에 응답하는 능력, 메시지가 명시된 클래스 등으로 구성된다.


메서드 사전의 내용을 검사하면 클래스가 특정 메시지 선택자를 구현하는지, 클래스가 메시지로 응답할 수 있는지, 어떤 메서드가 특정 변수나 리터럴을 참조하는지를 알아낼 수 있다. 이러한 메시지는 프로그래머가 시스템 내 객체의 기능성과 구조체를 탐구할 수 있는 프로그래밍 환경을 생성 시 유용하다.

메서드 딕셔너리 검사하기(testing the method dictionary)
hasMethods 수신자가 (로컬) 메서드 사전에 메서드를 갖고 있는지를 응답한다.
includesSelector: selector 인자 selector를 선택자로 가진 메시지가 수신자의 클래스의 로컬 메서드 사전에 있는지 응답한다.
canUnderstand: selector 인자를 선택자로 가진 메시지에 수신자가 응답할 수 있는지 여부를 응답한다. 선택자는 수신자의 클래스 또는 그 슈퍼클래스의 메서드 사전에 위치할 수 있다.
whichClassIncludesSelector: selector 수신자의 슈퍼클래스 사슬에서 selector 인자를 메시지 선택자로서 찾을 수 있는 첫 번째 클래스를 응답한다.
선택자를 포함하는 클래스가 없다면 nil을 응답한다.
whichSelectorAccess: instVarName 인자 instVarName에 명명된 인스턴스 변수로서 접근하는 메서드를 가진 수신자의 로컬 메서드 사전의 선택자로 이루어진 Set을 응답한다.
whichSelectorsReferTo: anObject 인자 anObject로 접근하는 메서드를 가진 선택자로 구성된 Set을 응답한다.
scopeHas: name ifTrue: aBlock 변수명 name이 수신자의 범위 내에 있는지, 수신자 또는 그 슈퍼클래스 중 하나에 변수로서 명시되어 있는지 결정한다. 만일 그렇다면 인자 aBlock을 평가한다.
Behavior 인스턴스 프로토콜


따라서 아래를 예로 들 수 있다.

표현식 결과
OrderedCollection includesSelector: #addFirst: true
SortedCollection includesSelector: #size false
SortedCollection canUnderstand: #size true
SortedCollection whichClassIncludesSelector: #size OrderedCollection
OrderedCollection whichSelectorsAccess: #firstIndex Set (makeRoomAtFirst before: size makeRoomAtLast insert:before: remove:ifAbsent: addFirst: first removeFirst find: removeAllSuchThat: at: at:put: reverseDo: do: setIndices:)


마지막 표현식 예제는 인스턴스 변수를 재명명하거나 삭제할 경우 변경해야 할 메서드를 결정 시 유용하다. 외부 접근을 목적으로 한 메시지 외에도 Set은 외부 메시지의 구현을 지원하기 위해 구현된 메시지를 모두 포함한다.


검사 프로토콜은 그 변수가 어떻게 저장되는지, 변수의 개수를 고정된 길이로 할 것인지 가변 길이로 할 것인지, 명명된 인스턴스 변수의 개수는 얼마나 되는지를 검사하기 위해 클래스로 전송되는 메시지를 포함한다.

인스턴스의 형태 검사하기(testing the form of the instances)
isPointers 수신자의 인스턴스의 변수가 포인터로 (워드) 저장되었는지 응답한다.
isBits 수신자의 인스턴스의 변수가 비트(포인터가 아니라)로 저장되었는지 응답한다.
isBytes 수신자의 인스턴스의 변수가 바이트로 (8-bit 정수) 저장되었는지 응답한다.
isWords 수신자의 인스턴스의 변수가 워드로 저장되었는지 응답한다.
isFixed 수신자의 인스턴스가 색인된 인스턴스 변수를 갖고 있지 않다면 true를 응답하고, 그 외의 경우 false를 응답한다.
isVariable 수신자의 인스턴스가 색인된 인스턴스 변수를 갖고 있다면 true를 응답하고, 그 외의 경우 false를 응답한다.
instSize 수신자의 명명된 인스턴스 변수 개수를 응답한다.
Behavior 인스턴스 프로토콜


그 예를 들어보면 다음과 같다.

표현식 결과
LinkedList isFixed true
String isBytes true
Integer isBits false
Float isWords true
OrderedCollection isFixed false
OrderedCollection instSize 2
oc ← OrderedCollection with: $a with: $b with: $c OrderedCollection($a $b $c)
oc size 3


마지막 네 가지 예는 OrderedCollection의 인스턴스가 가변 길이이고, oc 인스턴스는 세 개의 요소를 갖고 있음을 보여준다. 또한 OrderedCollection의 인스턴스는 두 개의 명명된 인스턴스 변수를 갖고 있다.


시스템에는 네 가지 유형의 클래스가 있다. 색인된 인스턴스 변수를 가진 클래스를 "variable-length"(가변 길이)라고 부르고, 그러한 변수가 없는 클래스를 "fixed-length"(고정 길이)라고 부른다. 모든 고정 길이 클래스의 변수는 포인터로서 (워드 크기의 참조) 저장된다. 가변 길이 클래스의 변수는 포인터, 바이트 또는 워드를 포함할 수 있다. 포인터는 워드 크기의 참조이므로 포인터를 포함하는 객체에게 워드를 포함하는지 질문할 경우 true를 응답하지만 그 반대로 질문하면 항상 동일한 결과를 발생시키지는 않는다. Class에 명시되고 뒷부분에 분류된 초기화 메시지는 각 클래스 유형의 생성을 지원한다.

클래스 계층구조 검사하기(testing the class hierarchy)
inheritsFrom: aClass 인자 aClass가 수신자의 슈퍼클래스의 사슬내에 있는지 응답한다.
kindOfSubclass 수신자를 클래스로 설명하는 키워드인 String을 응답하되, 일반 (고정 길이) 서브클래스, variableSubclass, variableByteSubclass, variableWordSubclass 중 하나에 해당한다.
Behavior 인스턴스 프로토콜


그 예는 다음과 같다.

표현식 결과
String inheritsFrom: Collection true
String kindOfSubclass 'variableByteSubclass:'
Array kindOfSubclass 'variableSubclass:'
Float kindOfSubclass 'variableWordSubclass:'
Integer kindOfSubclass 'subclass:'


❏ Behavior의 열거 프로토콜


Behavior 클래스에 명시된 메시지는 클래스와 연관된 특정 객체 집합을 열거하고 각각을 블록의 인자로 적용하도록 지원한다. 이러한 객체의 열거는 컬렉션 클래스에 제공된 것과 유사하며, 모든 서브클래스, 슈퍼클래스, 인스턴스, 서브클래스의 인스턴스의 열거로 구성된다. 또한 두 개의 메시지는 블록이 true로 평가하는 서브클래스 또는 슈퍼클래스의 선택을 지원한다.

열거하기(enumerating)
allSubclassesDo: aBlock 수신자의 서브클래스마다 인자 aBlock을 평가한다.
allSuperclassesDo: aBlock 수신자의 슈퍼클래스마다 인자 aBlock을 평가한다.
allInstancesDo: aBlock 수신자의 현재 인스턴스마다 인자 aBlock을 평가한다.
allSubinstancesDo: aBlock 수신자의 서브클래스의 현재 인스턴스마다 인자 aBlock을 평가한다.
selectSubclasses: aBlock 수신자의 서브클래스마다 인자 aBlock을 평가한다. aBlock이 true로 평가하는 서브클래스만 Set으로 수집한다. 결과가 되는 Set을 응답한다.
selectSuperclasses:aBlock 수신자의 슈퍼클래스마다 인자 aBlock을 평가한다. aBlock이 true로 평가하는 슈퍼클래스만 Set으로 수집한다. 결과가 되는 Set을 응답한다.
Behavior 인스턴스 프로토콜


가령 컬렉션 클래스의 인스턴스 행위를 이해하기 위해서는 Collection의 어떠한 서브클래스들이 addFirst: 메시지를 구현하는지 알고 있다면 유용하겠다. 이 정보를 이용해 프로그래머는 컬렉션으로 addFirst: 메시지가 전송되면 실제로 어떤 메서드가 평가되는지를 추적할 수 있다. 아래 표현식은 그러한 클래스를 subs라는 Set으로 수집한다.

subs  Set new.
Collection allSubclassesDo:
    [ :class |
        (class includesSelector: #addFirst:)
            ifTrue: [subs add: class]]


아래를 이용하면 동일한 정보로 접근 가능하다.

Collection selectSubclasses:
    [ :class | class includesSelector: #addFirst:]


두 가지 모두 세 개의 서브클래스, LinkedList, OrderedCollection, RunArray로 된 Set을 생성한다.


아래 표현식은 = 메시지를 구현하는 SmallInteger의 슈퍼클래스들로 이루어진 컬렉션을 리턴한다.

SmallInteger selectSuperclasses:
    [ :class | class includesSelector: # = ]


그에 대한 응답은 다음과 같다.

Set (Integer Magnitude Object)


Collection의 서브클래스 몇 가지가 first 메시지를 구현한다. 각 구현에 대한 코드 리스트를 확인하길 원한다고 가정해보자. 아래 표현식은 'classMethods.first'라는 이름을 가진 파일에 코드를 출력한다.

| aStream |
aStream  Disk file: 'classMethods.first'.
Collection allSubclassesDo:
    [ :class |
        (class includesSelector: #first)
            ifTrue:
                [class name printOn: aStream.
                    aStream cr.
                    (class sourceCodeAt: #first) printOn: aStream.
                    aStream cr; cr]].
aStream close


파일에 포함된 결과는 다음과 같다.

SequenceableCollection
'first
    self emptyCheck.
    ↑self at: 1'
OrderedCollection
'first
    self emptyCheck.
    self basicAt: firstIndex'
Interval
'first
    start'
LinkedList
'first
    self emptyCheck.
    firstLink'


다음 절에 설명된 프로토콜은 일반적으로 프로그래머는 사용하지 않지만 시스템 개발자라면 흥미를 가질 수도 있다. 설명된 메시지는 주로 그래픽 지향적 인터페이스에 표현된 메뉴에서 항목을 선정함으로써 프로그래밍 환경에서 접근한다.


클래스의 기능 대부분은 Behavior의 프로토콜에 명시되어 있지만 Behavior가 클래스에 대해 완전한 표현을 제공하지 않기 때문에 다수의 메시지를 구현할 수 없다. 특히 Behavior는 인스턴스 변수명과 클래스 변수명에 대한 표현도 제공하지 않고, 클래스에 대한 주석과 클래스명에 대한 표현도 제공하지 않는다.


클래스명, 클래스 주석, 인스턴스 변수명에 대한 표현은 Behavior의 서브클래스인 ClassDescription에 제공된다. ClassDescription에는 두 개의 서브클래스, Class와 Metaclass가 있다. Class는 클래스 변수명과 pool 변수에 대한 표현을 설명한다. 메타클래스는 유일한 인스턴스의 pool 변수와 클래스를 공유한다. Class는 클래스 변수와 pool 변수의 추가 및 제거, 그리고 다양한 유형의 서브클래스 생성을 위한 추가적 프로토콜을 추가한다. Metaclass는 그 자체의 서브클래스를 생성하기 위한 초기화 메시지, 즉 새로운 클래스에 대한 메타클래스를 생성하기 위한 메시지를 추가한다.


ClassDescription 클래스

ClassDescription은 클래스 명명하기, 클래스 주석 붙이기, 인스턴스 변수 명명하기를 표현한다. 이는 이름과 주석을 추가하고 인스턴스 변수를 추가 및 제거하기 위한 추가 프로토콜에 반영된다.

클래스 설명으로 접근하기(accessing class description)
name 수신자의 이름에 해당하는 String을 응답한다.
comment 수신자에 대한 주석에 해당하는 String을 응답한다.
comment: aString 수신자의 주석을 인자 aString으로 설정한다.
addInstVarName: aString 인자 aString을 수신자의 인스턴스 변수 중 하나로서 추가한다.
removeInstVarName: aString 수신자의 인스턴스 변수 중 하나인 인자 aString을 제거한다. aString을 찾을 수 없다면 오류를 보고한다.
ClassDescription 인스턴스 프로토콜


ClassDescription은 클래스의 설명에 구조화를 제공하기 위해 Class와 Metaclass에 공통된 슈퍼클래스로서 제공된다. 이는 일반적인 프로그램 개발 환경을 지원하도록 돕는다. 구체적으로 말해 ClassDescription은 메서드 사전에 대해 선택자/메서드 쌍을 조직하기 위한 구조체를 추가한다. 이러한 구조는 사전의 하위집합이 그룹화 및 명명되는 간단한 범주화 방식으로, 구체적으로 말하자면 본 서적의 여러 장을 통해 메시지를 그룹화하고 명명한 방식을 의미한다. ClassDescription은 외부 스트림(파일)에 전체 클래스 설명을 저장하는 메커니즘과 클래스 설명에 일어나는 변화가 기록(log)되는 메커니즘을 제공한다.


클래스 자체는 시스템 범주 분류로 그룹화된다. 본문에서 이와 관련된 장들의 구조는 시스템 클래스 범주의 구조와 일치하는데, 가령 크기(magnitudes), 숫자, 컬렉션, 커널 객체, 커널 클래스, 커널 지원을 예로 들 수 있겠다. 메시지와 클래스 범주화에 대한 프로토콜은 다음 메시지를 포함한다.

메시지와 클래스의 조직(organization of messages and classes)
category 수신자에 대한 시스템 조직 범주(system organization category)를 응답한다.
category: aString 시스템 범주 aString에서 수신자를 범주화하여 기존의 범주에서 수신자를 제거한다.
removeCategory: aString aString 이름으로 범주화된 각 메시지를 제거하고 범주 자체도 제거한다.
whichCategoryIncludesSelector: selector 수신자의 메서드 사전의 조직 내에서 인자 selector의 범주를 응답하고, 선택자를 찾을 수 없다면 nil을 응답한다.
ClassDescription 인스턴스 프로토콜


메시지의 범주화를 감안하면 ClassDescription은 하나의 메서드 사전에서 다른 메서드 사전으로 메시지를 복사하고 범주명을 유지 또는 변경할 수 있는 메시지 집합을 지원할 수 있다. 복사를 지원하는 메시지는 다음으로 구성된다.

copy: selector from: aClass
copy: selector from: aClass classified: categoryName
copyAll: arrayOfSelectors from: class
copyAll: arrayOfSelectors from: class classified: categoryName 
copyAllCategoriesFrom: aClass
copyCategory: categoryName from: aClass
copyCategory: categoryName 
    from: aClass
    classified: newCategoryName


컴파일된 메서드는 특정 범주에 위치해야 하므로 범주화 방식은 컴파일을 위한 프로토콜에 영향을 미친다. 두 가지 메시지가 제공되는데, compile: code classfied: categoryName과 compile: code classified: categoryName notifying: requestor가 그것이다.


다음 예제로 Behavior는 특수 출력 프로토콜을 지원하여 컴파일링 메시지에 대한 인자를 계산할 수 있음을 주목해야 한다. 이는 다음과 같다.

출력하기(printing)
classVariableString 수신자의 변수 선언에 각 클래스 변수의 이름을 포함하는 String을 응답한다.
instanceVariableString 수신자의 변수 선언에 각 인스턴스 변수의 이름을 포함하는 String을 응답한다.
sharedVariableString 수신자의 변수 선언에 각 pool 사전의 이름을 포함하는 String을 응답한다.
Behavior 인스턴스 프로토콜


AuditTrail로 명명된 클래스의 생성을 예로 들어보자. 이 클래스는 요소의 제거가 지원되지 않는다는 점만 제외하면 LinkedList와 정확히 동일하다. 따라서 클래스는 LinkedList의 접근, 검사, 추가, 열거 프로토콜을 복사하여 생성할 수 있다. AuditTrail의 요소는 audit 정보의 저장을 지원하는 Link의 서브클래스의 인스턴스라고 가정한다. 먼저 클래스를 생성해보자. LinkedList에 관한 내부 정보는 알지 못하는 것으로 가정하므로 슈퍼클래스명과 변수는 LinkedList로 메시지를 전송하여 접근해야 한다.

LinkedList superclass
    subclass: #AuditTrail
    instanceVariableNames: LinkedList instanceVariableString
    classVariableNames: LinkedList classVariableString
    poolDictionaries: LinkedList sharedPoolString
    category: 'Record Keeping'.


AuditTrail은 LinkedList에 대한 슈퍼클래스에 (LinkedList superclass) 해당하는 클래스가 무엇이든 그것의 서브클래스로서 생성된다. 이제 관심이 있는 범주를 LinkedList 클래스로부터 복사한다.

AuditTrail copyCategory: #accessing from: LinkedList.
AuditTrail copyCategory: #testing from: LinkedList.
AuditTrail copyCategory: #adding from: LinkedList.
AuditTrail copyCategory: #enumerating from: LinkedList.
AuditTrail copyCategory: #private from: LinkedList.


AuditTrail은 두 개의 인스턴스 변수명, firstLink와 lastLink를 선언하고, first, last, size, isEmpty, add:, addFirst:, addLast: 메시지를 복사하였다. 또 그 중 적어도 하나를 외부 메시지의 구현에서 필요로 한다는 가정을 내려 private 범주에 모든 메시지를 복사하였다.


ClassDescription 내에서 외부 스트림에 클래스 설명의 저장을 지원하는 메시지는 다음과 같다.

파일다루기(filing)
fileOutOn: aFileStream 인자 aFileStream이 접근하는 파일에 수신자의 설명을 저장한다.
fileOutCategory: categoryName 수신자와 확장자 '.st'가 결합된 이름을 가진 파일을 생성한다. 그 파일에 categoryName으로 범주화된 메시지의 설명을 저장한다.
fileOutChangedMessages: setOfChanges on: aFileStream 인자 setOfChanges는 변경된 클래스/메시지 쌍의 컬렉션이다. 이러한 쌍에 대한 설명은 인자 aFileStream이 접근하는 파일로 저장한다.
ClassDescription 인스턴스 프로토콜


다음 표현식을 평가하여 'AuditTrail.st' 파일에 AuditTrail 클래스의 설명을 쓸 수 있다.

AuditTrail fileOutOn: (Disk file: 'AuditTrail.st')


Metaclass 클래스

Smalltalk-80 시스템에서 메타클래스의 주요 목적은 클래스 변수를 초기화하고 메타클래스의 유일한 인스턴스에 대해 초기화된 인스턴스를 생성하기 위한 프로토콜을 제공하는 데에 있다. 따라서 Metaclass가 추가하는 키 메시지 자체가 초기화 메시지들인데, 그 중 하나는 서브클래스를 생성하기 위해 Metaclass 자체로 전송되고, 또 하나는 유일한 인스턴스를 생성하기 위해 Metaclass의 인스턴스로 전송된다.

인스턴스 생성
subclassOf: superMeta
||메타클래스 superMeta의 서브클래스인 Metaclass의 인스턴스를 응답한다.
name: newName
    newName
    environment: aSystemDictionary
    subclassOf: superClass
    instanceVariableNames: stringOfInstVarNames
    variable: variableBoolean
    words: wordBoolean
    pointers: pointerBoolean
    classVariableNames: stringOfClassVarNames
    poolDictionaries: stringOfPoolNames
    category: categoryName
    comment: commentString
    changed: changed
물론 이러한 인자 각각은 완전히 초기화된 클래스를 생성하는 데 필요하다.
Metaclass 클래스 프로토콜


Smalltalk-80 프로그래밍 환경은 사용자가 새로운 클래스를 생성하기 위해 그래픽 인터페이스 기법을 이용해 정보를 명시하는 간단한 방법을 제공한다.


Class 클래스

Class의 인스턴스는 객체의 표현과 행위를 설명한다. Class는 Behavior에 제공된 기본적인 것들에게는 전반적인 프로그래밍 지원 기능을 추가하고, ClassDescription에 제공된 것들에게는 좀 더 설명적인 기능을 추가한다. 특히 Class는 클래스 변수명과 공유(pool) 변수에 대한 표현을 추가한다.

인스턴스와 변수로 접근하기(accessing instances and variables)
addClassVarName: aString 인자 aString을 수신자의 클래스 변수로서 추가한다. aString의 첫 번째 문자는 대문자로 적어야 하고, aString가 이미 존재하는 클래스 변수명일 경우는 없다.
removeClassVarName: aString 인자 aString을 이름으로 가진 수신자의 클래스 변수를 제거한다. 클래스 변수가 아니거나 클래스의 메서드에서 여전히 사용 중이라면 오류를 보고한다.
addSharedPool: aDictionary 인자 aDictionary를 공유 변수의 pool로서 추가한다. 사전이 이미 수신자 내에 공유 pool일 경우 오류를 보고한다.
removeSharedPool: aDictionary 수신자의 pool 사전 중 하나인 인자 aDictionary를 제거한다. 사전이 수신자의 pools 중 하나가 아닐 경우 오류를 보고한다.
classPool 수신자의 클래스 변수의 사전을 응답한다.
initialize 클래스 변수를 초기화한다.
Class 인스턴스 프로토콜


추가 접근 메시지들은 클래스의 설명을 클래스와 동일한 이름으로 된 파일에 저장하고 (fileOut) 시스템으로부터 클래스를 제거한다 (removeFromSystem).


시스템 내에 네 가지 유형의 서브클래스 중 하나를 생성하기 위한 다양한 메시지가 Class의 메서드 사전에 명시된다. 또 Class는 클래스를 재명명하는 메시지를 제공하는데 (rename: aString), 이 메시지는 메타클래스로 전송하기에 적합한 메시지가 아니기 때문에 ClassDescription보다는 Class에서 제공된다.


instance creation
    subclass: classNameString
        instanceVariableNames: stringInstVarNames
        classVariableNames: stringOfClassVarNames
        poolDictionaries: stringOfPoolNames
        category: categoryNameString
수신자의 길이가 고정된 (일반) 서브클래스에 해당하는 새 클래스를 생성한다. 각 인자는 새로운 클래스를 초기화하고 범주화하는 데에 필요한 정보를 제공한다.
Class 인스턴스 프로토콜


첫 번째 키워드를 제외하고 위의 것과 동일한 세 개의 메시지, variableSubclass:, variableByteSubclass:, 또는 variableWordSubclass가 여러 유형의 클래스 생성을 지원한다. 시스템에서는 가변적 길이의 클래스의 서브클래스는 가변적 길이의 클래스가 되도록 요구한다는 사실을 명심한다. 가능하면 시스템이 적절한 규칙을 만드는데, 그렇지 않으면 프로그래머에게 오류가 보고된다.


새로운 서브클래스를 생성할 때마다 해당 클래스의 인스턴스 변수를 저장 및 검색하기 위한 메시지를 설치하길 원한다고 가정해보자. 예를 들어, 인스턴스 변수명으로 name과 address를 가진 Record 클래스를 생성한다면 이러한 변수의 값으로 응답하기 위해서는 name과 address 메시지를, 이러한 변수의 값을 메시지 인자의 값으로 설정하기 위해서는 name: argument와 address: argument를 제공하길 원한다. 이를 달성하는 한 가지 방법은 다음 메서드를 Class 클래스의 인스턴스 생성 프로토콜에 추가하는 것이다.

accessingSubclass: className
        instanceVariableNames: instVarString
        classVariableNames: classVarString
        poolDictionaries: stringOfPoolNames
        category: categoryName
    | newClass |
    newClass  self subclass: className
                instanceVariableNames: instVarString
                classVariableNames: classVarString
                poolDictionaries: stringOfPoolNames
                category: categoryName.
    newClass instVarNames do:
        [ :aName |
            newClass compile: (aName , '
        ↑', aName) classified: #accessing.
            newClass compile: (aName , ': argument
        ', aName, ' ← argument.
        ↑argument') classified: #accessing].
    newClass


보통대로 클래스를 생성한 다음 각 인스턴스 변수명마다 두 개의 메서드를 컴파일한다. 첫 번째는 다음 형태로 되어 있고,

name
    name


두 번째는 다음 형태로 되어 있다.

name: argument
    name  argument.
    argument


따라서 Record 클래스를 생성하면 다음 메시지를 Object로 전송하면 된다.

Object accessingSubclass: #Record
    instanceVariableNames: 'name address'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Example'.


메시지는 Class의 메서드 사전에서 찾을 수 있으며, Record 클래스의 accessing 범주에 다음과 같이 4개의 메시지를 생성한다.

accessing
    name
        name
    name: argument
        name  argument.
        argument
    address
        address
    address: argument
        address  argument.
        argument


Notes