Smalltalk80LanguageImplementationKor:Chapter 06

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 6 장 모든 객체에 대한 프로토콜

모든 객체에 대한 프로토콜

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


시스템의 모든 것은 객체다. 시스템 내 모든 객체에 공통되는 프로토콜은 Object 클래스의 설명에서 제공된다. 즉, 시스템에서 생성된 모든 객체는 Object 클래스가 정의한 메시지에 응답할 수 있다는 말이다. 이는 주로 새로운 메시지를 추가하거나 기존 메시지에 대한 응답을 수정함으로써 새로운 유형의 객체를 개발할 시작 장소를 제공하기에 적당한 기본 행위를 지원하는 메시지들이다. Object의 프로토콜을 연구할 때 고려해야 하는 예제들은 3이나 16.23과 같은 숫자형 객체, 'this is a string' 또는 #(this is an array)과 같은 컬렉션, nil 또는 ture, Collection이나 SmallInteger와 같은 클래스 설명 객체, 혹은 Object 자체가 있다.


이번 장에 주어진 Object 클래스에 대한 프로토콜의 명세는 불완전하다. 메시지 처리, 특수 의존성 관계, 시스템 프리미티브에 관한 메시지는 누락되었다. 이와 관련된 내용은 제 14장에 실려 있다.


객체의 기능 검사하기

모든 객체는 어떤 클래스의 인스턴스다. 객체의 기능은 그 클래스에 의해 결정된다. 이러한 기능은 두 가지 방식으로 검사되는데, 먼저 객체의 클래스인지 슈퍼클래스인지 결정하기 위해 클래스를 명시적으로 명명하는 방법과, 이에 대해 객체가 응답할 수 있는지 결정하기 위해 메시지 선택자를 명명하는 방법이 있다. 이는 여러 클래스의 인스턴스들 간 관계를 고려하는 두 가지 방법을 반영하는데, 바로 클래스/서브클래스 계층구조와 공유된 메시지 프로토콜 관계가 그것이다.

기능 검사하기
class 수신자의 클래스에 해당하는 객체를 응답한다.
isKindOf: aClass 인자 aClass가 수신자의 슈퍼클래스인지 클래스인지 응답한다.
isMemberOf: aClass 수신자가 인자 aClass의 직접 인스턴스인지 응답한다. 수신자에게 메시지 클래스를 전송 시 응답이 aClass와 동일한지 (==) 여부를 검사하는 것과 동일하다.
respondsTo: aSymbol 수신자의 클래스 또는 그 슈퍼클래스 중 하나의 메서드 사전이 인자 aSymbol을 메시지 선택자로 포함하는지 여부를 응답한다.
Object 인스턴스 프로토콜


메시지 예제와 그에 해당하는 결과는 다음과 같다.

표현식 결과
3 class SmallInteger
#(this is an array) isKindOf: Collection true
#(this is an array) isMemberOf: Collection false
#(this is an array) class Array
3 respondsTo: #isKindOf: true
#(1 2 3) isMemberOf: Array true
Object class Object class


객체 비교하기

시스템 내 모든 정보는 객체로 표현되므로 객체를 복사하고 객체의 식별을 검사하는 데에는 기본적인 프로토콜만 제공된다. Object 객체에 명시된 중요한 비교는 동등성(equivalence)과 상등성(equality) 검사가 된다. 동등성(==)은 두 객체가 동일한 객체인지 검사하는 것이다. 상등성(=)은 두 객체가 동일한 구성요소를 나타내는지에 대한 검사다. "동일한 구성요소를 나타내는지" 에 관련된 결정은 메시지의 수신자가 내리는 것으로, 보통 새로운 인스턴스 변수를 추가하는 이러한 새로운 유형의 객체가 상등성 검사에 입력되어야 하는 인스턴스 변수를 명시하기 위해서는 = 라는 메시지를 구현해야 한다. 예를 들어, 두 배열의 상등성은 배열의 크기를 검사한 다음 배열 내 각 요소의 상등성을 확인하여 이루어지고, 두 숫자의 상등성은 두 개의 숫자가 동일한 값을 나타내는지 검사를 통해 결정되며, 두 계좌(bank account)의 상등성은 각 계좌 식별 숫자의 상등성에만 의존할 수도 있다.


hash 메시지는 비교 프로토콜에서 특별한 부분에 해당한다. hash에 대한 응답은 정수로 되어 있다. 서로 같은 두 개의 객체는 hash에 대해 동일한 값을 리턴해야만 한다. 같지 않은(unequal) 객체는 hash에 대해 같은 값을 리턴할 수도 그렇지 않을 수도 있다. 보통 이러한 정수는 색인된 컬렉션에서 객체를 위치시키기 위해 색인으로서 사용된다 (제 3장에 설명한 바와 같이). =가 재정의될 때마다 같은 두 객체가 hash에 같은 값을 리턴하는 프로퍼티를 유지하기 위해서는 hash를 재정의해야 할 것이다.

comparing
== anObject Answer whether the receiver and the argument are the same object.
= anObject Answer whether the receiver and the argument represent the same component.
~= anObject Answer whether the receiver and the argument do not represent the same component.
~~ anObject Answer whether the receiver and the argument are not the same object.
hash Answer an Integer computed with respect to the representation of the receiver.
Object instance protocol


=의 기본 구현은 ==의 기본 구현과 동일하다.


nil 객체와 정체성을 간단하게 검사하는 방법을 제공하는 특수화된 비교 프로토콜이 몇 가지 있다.

검사하기
isNil 수신자가 nil인지 응답한다.
notNil 수신자가 nil이 아닌지 응답한다.
Object 인스턴스 프로토콜


이러한 메시지는 각각 == nil 그리고 ~~nil과 동일하다. 어떤 것을 사용하는지는 개인의 취향에 따른다.


몇 가지 분명한 예로 다음을 들 수 있다.

표현식 결과
nil isNil true
true notNil true
3 isNil false
#(a b c) = #(a b c) true
3 = (6/2) true
#(1 2 3) class == Array true


객체 복사하기

객체를 복사하는 방법에는 두 가지가 있다. 두 가지 방법은 객체의 변수 값이 복사되었는지 유무에 따라 구별된다. 값이 복사되지 않았다면 공유되고 (shallowCopy), 값이 복사되었다면 공유되지 않는다 (deepCopy).

복사하기
copy 수신자와 같이 또 다른 인스턴스를 응답한다.
shallowCopy 수신자의 인스턴스 변수를 공유하는 수신자의 복사본을 응답한다.
deepCopy 각 인스턴스 변수에 대해 고유의 복사본을 가진 수신자의 복사본을 응답하라.
Object 인스턴스 프로토콜


copy의 기본 구현은 shallowCopy다. 복사가 공유 및 비공유 변수의 특별한 조합을 야기해야 하는 서브클래스에서는 shallowCopy 또는 deepCopy와 연관된 메서드가 아니라 copy와 연관된 메서드가 주로 구현된다.


예로, Array의 복사본은 (얕은 복사) 원본 Array에서와 동일한 요소를 참조하지만 복사본은 다른 객체에 해당한다. 복사본에서 요소를 대체한다고 해서 원본이 변경되지는 않는다.

표현식 결과
a ← #('first' 'second' 'third') ('first' 'second' 'third')
b ← a copy ('first' 'second' 'third')
a = b true
a == b false
(a at: 1) == (b at: 1) true
b at: 1 put: 'newFirst' 'newFirst'
a = b false
a ← 'hello' 'hello'
b ← a copy 'hello'
a = b true
a == b false


따라서 그림 6.1은 얕은 복사와 깊은 복사의 관계를 보여준다. shallowCopy와 deepCopy의 차이를 더 알아보려면 PersonnelRecord를 예로 살펴본다. 이것이 Insurance 클래스의 인스턴스인 insurancePlan 변수를 포함하도록 정의되었다고 가정하자. Insurance의 각 인스턴스는 그것과 연관된 값을 가지며 의료 혜택에서 제한을 나타낸다고 가정해보자. 이제 employeeRecord를 PersonnelRecord의 원형적(prototypical) 인스턴스로 생성했다고 치자. "Prototypical"(원형적)이란 객체가 그 클래스의 새로운 인스턴스에 대한 모든 초기 속성을 갖고 있어서 초기화 메시지의 시퀀스를 전송하는 대신 그냥 복사만 해도 인스턴스가 생성된다는 말이다. 이런 원형적 인스턴스가 PersonnelRecord의 클래스 변수이고, 새로운 PersonnelRecord를 생성하는 데에 대한 응답으로 그것의 얕은 복사가 이루어진다고 가정하면, new 메시지와 연관된 메서드는 ↑employeeRecord copy가 된다.

그림 6-1


아래의 표현식을 평가한 결과,

joeSmithRecord   PersonnelRecord new


joeSmithRecord는 employeeRecord의 복사본을 (특히 얕은 복사) 참조한다.


원형 employeeRecord와 실제 기록인 joeSmithRecord는 동일한 의료 보험으로 참조를 공유한다. 회사 방침은 변경될 수도 있다. PersonnelRecord의 원형적 인스턴스를 가짐으로써 구현되는 changeInsuranceLimit: aNumber를 PersonnelRecord가 이해한다고 가정하고 의료 혜택에 대한 의료 보험 한계를 리셋한다. 해당 의료 계획은 공유되기 때문에 아래의 표현식을 평가하면,

PersonnelRecord changeInsuranceLimit: 4000


모든 직원의 의료 혜택이 변경되는 결과를 낳는다. 예제에서는 employeeRecord와 그 복사본인 joeSmithRecord가 모두 참조하는 의료 혜택이 변경된다. 또 PersonnelRecord 클래스로 changeInsuranceLimit: 메시지가 전송되는데, 그 모든 인스턴스에게 변경 내용을 알리는 데에 적합한 객체이기 때문이다.


객체의 일부로 접근하기

Smalltalk-80 시스템에는 색인된 변수가 있는 객체와 명명된 변수가 있는 객체, 두 가지 유형의 객체가 있다. 색인된 변수가 있는 객체는 명명된 인스턴스 변수도 가질 수 있다. 둘의 차이는 제 3장에서 설명한 바 있다. Object 클래스는 객체의 색인된 변수로 접근하도록 아래와 같은 6개의 메시지를 지원한다.

접근하기
at: index 인자 index를 색인으로 가진 수신자의 색인된 인스턴스 변수 값을 응답한다. 수신자가 색인된 변수를 갖고 있지 않거나 인자가 색인된 변수의 개수보다 클 경우 오류를 보고한다.
at: index put: anObject index 인자를 색인으로 가진 수신자의 색인된 인스턴스 변수 값으로 인자 anObject를 저장한다. 수신자가 색인된 변수를 갖고 있지 않거나 인자가 색인된 변수의 개수보다 클 경우 오류를 보고한다. anObject를 응답한다.
basicAt: index at: index와 동일하다. 하지만 이 메시지와 연관된 메서드는 어떤 서브클래스에서도 수정이 불가하다.
basicAt: index put: anObject at: index put: anObject와 동일하다. 하지만 이 메시지와 연관된 메서드는 어떤 서브클래스에서도 수정이 불가하다.
size 수신자의 색인된 변수의 개수를 응답한다. 이 값은 해당하는(legal) 색인 중에서 가장 큰 색인과 동일하다.
basicSize size와 동일하다. 하지만 이 메시지와 연관된 메서드는 어떤 서브클래스에서도 수정이 불가하다.
Object 인스턴스 프로토콜


접근하는 메시지는 쌍으로 되어 있음을 주목하라. 그 중 한 메시지에는 basic이라는 단어가 앞에 붙는데, 어떤 서브클래스에서도 구현이 수정되어선 안 되는 기본 시스템 메시지라는 의미다. 메시지 쌍을 제공하는 목적은 특별한 경우를 처리하기 위해 외부 프로토콜 at:, at:put:, size를 오버라이드하면서 프리미티브 메서드로 계속 접근할 방도를 유지하는 데에 있다. (제 4장에서 시스템의 가상 머신에 구현된 메서드인 “primitive”(프리미티브) 메서드를 설명하였다.) 따라서 클래스 설명의 계층구조에 있는 어떤 메서드에서든 프리미티브 구현을 얻으려면 basicAt:, basicAt:put:, basicSize 메시지를 언제든 이용할 수 있다. basicSize 메시지는 어떤 객체로든 전송이 가능하며, 객체가 가변적(variable) 길이가 아닌 경우 응답은 0이 된다.


Array 클래스의 인스턴스는 길이가 가변적인 객체들이다. letters가 Array #(a b d f j m p s)라고 가정해보자.

expression result
letters size 8
letters at: 3 d
letters at: 3 put: #c c
letters (a b c f j m p s)


객체를 출력하고 저장하기

객체의 설명을 제공하는 문자 시퀀스를 생성하는 방법에는 여러 가지가 있다. 설명은 객체의 정체성과 관련된 단서만 제공할 것이다. 아니면 비슷한 객체가 구성되기에 충분한 정보를 제공하는 수도 있다. 정체성에 대한 단서를 제공하는 경우(출력), 설명은 Lisp pretty-printing 루틴이 제공하는 것처럼 잘 포맷팅되고 시각적으로 유쾌한 스타일일 수도, 그렇지 않을 수도 있다. 수도 있다. 두 번째의 경우(저장), 설명은 다른 객체와 공유하는 정보를 유지할 수 있다.


Smalltalk-80 시스템에서 클래스의 메시지 프로토콜은 출력과 저장을 지원한다. Object 클래스에서 이러한 메시지들의 구현은 최소한의 기능만 제공하며, 대부분의 서브클래스는 생성된 설명을 개선하기 위해 메시지를 오버라이드한다. 두 가지 메시지에 대한 인자는 Stream 유형의 인스턴스들로, Stream은 제 12장에서 소개할 것이다.

인쇄하기
printString 수신자의 설명을 문자로 가진 String을 응답한다.
printOn: aStream aStream 인자 뒤에 수신자의 설명을 문자로 가진 String을 붙인다.
저장하기
storeString 수신자를 재구성할 수 있는 수신자의 String 표현을 응답한다.
storeOn: aStream 인자 aStream 뒤에 수신자를 재구성할 수 있는 수신자의 String 표현을 붙인다.
Object 인스턴스 프로토콜


두 가지 유형의 출력은 디스플레이 화면에 표시되거나, 파일에 작성되거나, 네트워크를 통해 전송되는 문자의 시퀀스를 생성하는 데에 기반을 둔다. storeString 또는 storeOn: 이 생성한 시퀀스는 객체를 재구성하기 위해 평가할 수 있는 하나 또는 그 이상의 표현식으로 해석 가능해야 한다. 따라서 가령 $a, $b, $c, 세 개의 요소로 된 aSet은 아래와 같이 출력될 수 있고,

Set ($a $b $c)


아래와 같이 저장될 수 있다.

(Set new add: $a, add: $b, add: $c)


리터럴은 출력과 저장에 동일한 표현을 사용할 수 있다. 따라서 String 'hello'는 'hello'를 출력하고 저장할 것이다. Symbol #name은 name을 출력하지만 #name로 저장한다.


그 외에도 printString의 기본 구현은 객체의 클래스명이고, storeString의 기본 구현은 클래스명 다음에 인스턴스 생성 메시지 basicNew가 따라온 뒤에 각 인스턴스 변수를 저장하기 위한 메시지 시퀀스가 따라온다. 예를 들어, Object 의 서브클래스, 즉 Example 클래스가 기본 행위를 보였다면 인스턴스 변수가 없는 Example의 인스턴스인 eg의 경우 다음과 같은 결과가 발생할 것이다.

expression result
eg printString 'an Example'
eg storeString '(Example basicNew)'


오류 처리

모든 처리가 객체로 메시지를 전송하여 실행된다는 사실은 시스템에서 처리해야만 하는 기본 오류 상태가 한 가지 있다는 의미인데, 메시지가 객체로 처리되었지만 메시지가 객체의 슈퍼클래스 사슬 내 어떤 클래스에서도 명시되지 않을 때를 나타낸다. 이러한 오류는 doesNotUnderstand: aMessage 메시지를 원본 객체로 전송하는 해석기(interpreter)가 결정한다. aMessage 인자는 실패한 메시지 선택자와 그에 연관된 인자가 존재할 경우 그것을 표현한다. doesNotUnderstand: 와 연관된 메서드는 사용자에게 오류가 발생했음을 보고한다. 보고가 사용자에게 어떻게 표시되는지는 시스템이 지원하는 (그래픽) 인터페이스의 기능에 해당하므로 본문에서 다루지는 않겠지만, 대화형 시스템에서는 오류 메시지가 사용자에 출력 장치로 출력되고 나서 사용자에게 오류의 상황을 수정할 수 있는 기회를 제공하는 것을 최소 사양으로 한다. 제 17장에서는 Smalltalk-80 시스템 오류 알림과 디버깅 메커니즘을 설명할 것이다.


기본 오류 조건뿐만 아니라 메서드는 테스트에서 사용자 프로그램이 허용할 수 없는 일을 하려는 것으로 판단되면 명시적으로 시스템 오류 처리 메커니즘을 사용할 수도 있다. 이러한 경우 메서드는 사용자에게 표시되어야 하는 오류 설명을 명시하길 원할 것이다. 보통은 인자를 통해 원하는 설명을 나타내는 error: aString 메시지를 활성화된 인스턴스로 전송한다. 시스템 알림 메커니즘을 호출하는 것이 기본 구현이다. 아니면 프로그래머가 애플리케이션 의존적인 오류 보고를 사용하는 error: 의 구현을 제공하는 것도 대안 방법이 되겠다.


공통된 오류 메시지는 Object 클래스의 프로토콜에서 지원된다. 오류 메시지는 시스템 프리미티브가 실패하였다거나, 서브클래스가 지원할 수 없는 상속된 메시지를 오버라이드하여 사용자가 호출해서는 안된다거나, 슈퍼클래스가 서브클래스에서 구현되어야 하는 메시지를 명시했다거나 하는 등의 보고를 할 수 있다.

error handling
doesNotUnderstand: aMessage 수신자가 aMessage 인자를 메시지로 이해하지 못한다고 사용자에게 보고한다.
error: aString 수신자에게 메시지를 응답하는 환경에서 오류가 발생하였음을 사용자에게 보고한다. 보고는 오류 알림 설명의 일부로 aString 인자를 사용한다.
primitiveFailed 시스템 프리미티브로 구현된 메서드가 실패하였음을 사용자에게 보고한다.
shouldNotImplement 수신자의 슈퍼클래스는 서브클래스에서 메시지가 구현되어야 한다고 명시하지만 수신자의 클래스는 적절한 구현을 제공할 수 없음을 사용자에게 보고한다.
subclassResponsibility 수신자의 슈퍼클래스에 명시된 메서드가 수신자의 클래스에서 구현되어야 했음을 사용자에게 보고한다.
Object 인스턴스 프로토콜


서브클래스는 오류가 있는 상황을 수정하는 특별 지원을 제공하기 위해 오류 처리 메시지의 오버라이드를 선택할 수 있다. 위에 소개한 메시지 중 마지막 두 개의 사용 예는 컬렉션 클래스의 구현을 다룬 제 13장에서 제공된다.


Notes