Smalltalk80LanguageImplementationKor:Chapter 12

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 12 장 스트림에 대한 프로토콜

스트림에 대한 프로토콜

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***
                ReadWriteStreamm***
                    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


컬렉션 클래스는 객체를 모두 비선형 그룹과 선형 그룹으로 저장하기 위한 기본 데이터 구조체를 제공한다. 이러한 클래스에 대한 프로토콜은 개별 요소를 직접 접근하도록 (저장 및 검색하도록) 해준다. 또 열거 메시지를 통해 모든 요소를 방해하지 않고 순서대로 접근하도록 지원한다. 하지만 두 유형의 접근 연산의 결합은 지원하지 않으므로 열거와 저장의 동시 지원은 하지 않는다. 컬렉션 프로토콜은 그 외에도 외부 위치 참조가 따로 유지되지 않는 한 각 요소를 한 번에 하나씩 접근하는 기능도 지원하지 않는다.


각 요소에 쉽게 계산된 외부 이름이 존재하지 않는 한 각 요소의 인터럽트 가능한 (interruptible) 열거는 효율적으로 실행될 수 없다. 따라서 컬렉션의 요소들이 유일한 경우에 한해 first와 after: 의 결합을 이용해 OrderedCollection의 요소를 순차적으로 읽는 것이 가능하다. 그 외에 대안적 방법은 컬렉션 자체가 어떤 요소가 마지막으로 접근되었는지 어떻게 해서든 기억하는 방법이 있다. 이를 "position reference"(위치 참조)라고 부르겠다. 하지만 요소의 시퀀스에 대한 공유 접근의 가능성이 있다는 것은 마지막으로 접근한 요소에 대해 구분된 외부 메모리를 유지할 필요가 있음을 의미한다.


Stream 클래스는 위치 참조를 객체의 컬렉션에서 유지하는 기능을 나타낸다. "Streaming over a collection"(컬렉션에서 스트리밍)이란 각 요소를 한 번에 하나씩 열거 또는 저장하거나 어쩌면 두 개를 혼합하여 사용하는 것이 가능하도록 컬렉션의 요소로 접근함을 의미한다. 동일한 컬렉션 상에서 여러 개의 Streams를 생성하면 동일한 컬렉션에 다수의 위치 참조를 유지하는 것이 가능하다.


컬렉션에서 스트리밍을 위한 위치 참조를 유지하는 방법에는 다수가 있다. 가장 흔한 방법은 정수를 색인으로서 사용하는 방법이다. 이 접근법은 외부적으로 정수가 명명한 요소를 가진 컬렉션이라면 어디든 사용할 수 있다. SequenceableCollections는 모두 이 범주에 속한다. 앞서 설명하였듯 그러한 Stream은 Smalltalk-80 시스템에서 PositionableStream 클래스에 의해 표현된다. 위치 참조를 유지하는 두 번째 방법은 객체의 생성자에 대한 "seed"(시드)를 사용하는 것이다. Smalltalk-80 시스템에서 이러한 Stream 유형의 예로는 제 8장에서 이미 소개한 Random 클래스를 들 수 있다. 그리고 세 번째 방법은 시퀀스에서 노드로의 참조와 같이 비수치형 위치 참조를 유지하는 방법으로, 이번 장에서 연결 리스트 또는 트리 구조체에서 스트리밍을 지원하는 클래스 예제를 통해 설명하고 있다.


Stream 클래스

Object 클래스의 서브클래스인 Stream 클래스는 컬렉션에 걸친 스트리밍을 위해 접근 프로토콜을 명시하는 슈퍼클래스다. 해당 프로토콜에는 컬렉션으로 읽고(검색) 쓰는(저장) 메시지가 포함되어 있는데, Stream 클래스의 모든 서브클래스가 이 두 가지 접근 연산을 모두 지원하는 건 아니다. 기본 읽기 메시지는 next: 로, 컬렉션에서 Stream이 참조하는 다음 요소를 응답한다. 다음 요소로 접근하는 기능을 고려해보면 좀 더 일반적인 읽기 메시지를 지원할 수 있다. 이러한 메시지로는 요소의 anInteger 개수로 된 컬렉션으로 응답하는 next: anInteger, 다음 요소를 읽고 그것이 인자와 동일한지 응답하는 nextMatchFor: anObject, 모든 요소의 컬렉션을 응답하는 contents가 있다.


접근하기--읽기(accessing--reading)
next 수신자가 접근 가능한 다음 객체를 응답한다.
next: anInteger 수신자가 접근 가능한 객체의 다음 anInteger 개수를 응답한다. 일반적으로 수신자가 접근하는 것과 동일한 클래스의 컬렉션을 응답할 것이다.
nextMatchFor: anObject 다음 객체로 접근하고 그것이 anObject 인자와 동일한지 응답한다.
contents 수신자가 접근하는 컬렉션에서 모든 객체를 응답한다. 주로 수신자가 접근하는 것과 동일한 클래스의 컬렉션을 응답할 것이다.
Stream 인스턴스 프로토콜


기본 쓰기 메시지는 nextPut: anObject로, anObject 인자를 수신자가 접근할 수 있는 다음 요소로 저장함을 의미한다. 쓰기 메시지와 읽기 메시지를 모두 이용 가능할 경우, nextPut: anElement 다음에 next 메시지를 사용하면 방금 저장된 요소는 읽지 않고 컬렉션에서 그 다음 요소를 읽을 것이다. 쓰기 메시지로는 수신자가 접근하는 컬렉션으로 인자의 모든 요소를 저장하는 nextPutAll: aCollection, anObjcet 인자를 요소의 다음 anInteger 개수로 저장하는 next: anInteger put: anObject가 있다.

접근하기--쓰기(accessing--writing)
nextPut: anObject anObject 인자를 수신자가 접근 가능한 다음 요소로 저장한다. anObject를 응답한다.
nextPutAll: aCollection aCollection 인자에 요소를 수신자가 접근 가능한 다음 요소로 저장한다. aCollection을 응답한다.
next: anInteger put: anObject anObject를 수신자가 접근 가능한 요소의 다음 anInteger 개수로 저장한다. anObject를 응답한다.
Stream 인스턴스 프로토콜


읽기와 쓰기 메시지는 각각 다음 요소가 읽기 가능한지 혹은 쓰기 가능한지 판단하고, 읽기 또는 쓰기가 불가능할 경우 오류가 보고된다. 프로그래머는 그에 따라 접근이 여전히 실행 가능한지 판단하길 원할 것인데, 이는 Stream으로 atEnd 메시지를 전송하면 가능하다.

검사하기(testing)
atEnd 수신자가 더 이상 객체로 접근할 수 없는지 응답한다.
Stream 인스턴스 프로토콜


블록에 인자로서 적용되는 요소를 방해받지 않고 읽기 위해서는 컬렉션 클래스가 지원하는 열거 메시지와 비슷한 do: aBlock 메시지를 전송하면 실행 가능하다.

열거하기(enumerating)
do: aBlock 수신자가 접근할 수 있는 나머지 요소마다 aBlock 인자를 평가한다.
Stream 인스턴스 프로토콜


이 열거 메시지의 구현은 atEnd 와 next 메시지를 메시지 수신자에게 전송하는 것에 따라 좌우된다. 이러한 메시지 사용의 예로 해당 방법을 소개하겠다.

do: aBlock
    [self atEnd] whileFalse: [aBlock value: self next]


Stream 유형마다 그 인스턴스 생성 메시지를 명시해야 한다. 일반적으로 Stream은 new 메시지를 전송해서는 생성될 수 없는데 Stream은 그것이 어떤 컬렉션으로 접근하는지와 그것의 초기 위치 참조는 무엇인지에 대해 알고 있어야 하기 때문이다.


간단한 예로, Stream이 접근하는 컬렉션은 Array이고 Stream을 accessor라고 부른다고 가정해보자. Array의 내용은 Symbols이며,

Bob Dave Earl Frank Harold Jim Kim Mike Peter Rick Sam Tom


Bob이 다음으로 접근 가능한 요소가 되도록 위치 참조가 이루어진다. 그렇다면 아래의 결과가 나온다.

표현식 결과
accessor next Bob
accessor next Dave
accessor nextMatchFor: #Bob false
accessor nextMatchFor: #Frank true
accessor next Harold
accessor nextPut: #James James
accessor contents (Bob Dave Earl Frank Harold James Kim Mike Peter Rick Sam Tom)
accessor nextPutAll: #(Karl Larry Paul) (Karl Larry Paul)
accessor contents (Bob Dave Earl Frank Harold James Karl Larry Paul Rick Sam Tom)
accessor next: 2 put: #John John
accessor contents (Bob Dave Earl Frank Harold James Karl Larry Paul John John Tom)
accessor next Tom
accessor atEnd true


위치 설정 가능한 스트림(Positionable Streams)

본 장의 시작 부분에 우리는 위치 참조를 유지하기 위해 Stream이 사용할 수 있는 접근법으로 세 가지를 제시하였다. 첫 번째는 Stream으로 접근할 때마다 증가하는 정수 색인을 사용한다. Stream은 요소들이 정수를 외부 키로 갖고 있는 유형의 컬렉션으로만 접근하는데, SequenceableCollection 의 모든 서브클래스가 이에 포함된다.


PositionableStream 클래스는 Stream 클래스의 서브클래스다. 이는 위치 참조의 위치를 변경할 수 있는 Streams에 적절한 추가 프로토콜을 제공하지만 이것은 next와 nextPut: anObject 라는 상속된 메시지의 구현을 제공하지 않으므로 추상 클래스에 해당한다. 이러한 메시지들의 구현은 PositionableStream의 서브클래스, 즉 ReadStream, WriteStream, ReadWriteStream에게 맡긴다.


PositionableStream은 두 개의 인스턴스 생성 메시지, on: aCollection과 on: aCollection from: firstIndex to: lastIndex 중에 하나를 클래스로 전송하여 생성된다. aCollection 인자는 Stream이 접근하는 컬렉션이고, 두 번째는 aCollection의 하위컬렉션의 복사본으로 접근하는데, 이러한 복사본은 가령 두 개의 인자, firstIndex와 lastIndex 로 구분되어 있다.

인스턴스 생성(instance creation)
on: aCollection aCollection 인자에서 스트리밍하는 PositionableStream 유형의 인스턴스를 응답한다.
on: aCollection from: firstIndex to: lastIndex aCollection 인자의 하위컬렉션의 복사본에서 firstIndex부터 lastIndex까지 스트리밍하는 PositionableStream 유형의 인스턴스를 응답한다.
PositionableStream 클래스 프로토콜


PositionableStream은 컬렉션의 내용으로 접근하고 검사하기 위한 추가 프로토콜을 지원한다.

검사하기(testing)
isEmpty 수신자가 접근하는 컬렉션에 어떤 요소도 없을 경우 true를 응답하고, 그 외의 경우 false를 응답한다.
접근하기(accessing)
peek 컬렉션에서 다음 요소를 응답하되 (next 메시지에서와 마찬가지로), 위치 참조를 변경하지 않는다. 수신자가 끝에 위치할 경우 nil을 응답한다.
peekFor: anObject peek 메시지에 대한 응답을 결정한다. anObject 인자와 동일하다면 위치 참조를 증가시키고 true를 응답한다. 그 외의 경우 false를 응답하고 위치 참조를 변경하지 않는다.
upTo: anObject 수신자가 접근하는 다음 요소부터 시작해 그 다음으로 anObject와 동일한 요소까지(단 그 요소는 포함하지 않고) 컬렉션을 응답한다. anObject가 컬렉션에 포함되지 않은 경우 나머지 컬렉션을 모두 응답한다.
reverseContents 수신자의 내용의 복사본을 역순으로 응답한다.
PositionableStream 인스턴스 프로토콜


PositionableStream은 명시적 위치 참조를 저장하는 것으로 알려져 있으므로 그 참조로 접근하기 위한 프로토콜이 지원된다. 특히 참조는 컬렉션의 시작, 끝, 또는 다른 위치로 접근하도록 리셋이 가능하다.

위치 설정(positioning)
position 컬렉션으로 접근하기 위한 수신자의 현재 위치 참조를 응답한다.
position: anInteger 컬렉션으로 접근하기 위한 수신자의 현재 위치 참조를 anInteger 인자로 설정한다. 인자가 수신자의 컬렉션 범위를 벗어난 경우 오류를 보고한다.
reset 수신자의 위치 참조를 컬렉션의 시작으로 설정한다.
setToEnd 수신자의 위치 참조를 컬렉션의 끝으로 설정한다.
skip: anInteger 수신자의 위치 참조를 현재 위치에 anInteger를 더한 값으로 설정하고, 컬렉션의 범위 안에 유지되도록 결과를 조정하도록 한다.
skipTo: anObject 수신자의 위치 참조를 컬렉션에서 anObject 인자의 다음 발생을 넘도록 설정한다. 그러한 발생이 존재했는지 여부를 응답한다.
PositionableStream 인스턴스 프로토콜


ReadStream 클래스

ReadStream 클래스는 그 컬렉션으로부터 요소의 읽기만 가능한 접근자를 나타내는 PositionableStream의 구체적 서브클래스다. PositionableStream 클래스에 제공되고 모든 ReadStreams에 의해 상속된 추가 프로토콜의 사용을 보이기 위해 앞의 예제와 비슷한 예를 만들 수 있다. nextPut:, next:put:, nextPutAll: 메시지는 모두 ReadStream으로 성공적으로 전송할 수 없다.

accessor  ReadStream on: #(Bob Dave Earl Frank Harold Jim Kim Mike Peter Rick Sam Tom)


표현식 결과
accessor next Bob
accessor nextMatchFor: #Dave true
accessor peek Earl
accessor next Earl
accessor peekFor: #Frank true
accessor next Harold
accessor upTo: #Rick (Jim Kim Mike Peter)
accessor position 10
accessor skip: 1 the accessor itself
accessor next Tom
accessor atEnd true
accessor reset the accessor itself
accessor skipTo: #Frank true
accessor next Harold


WriteStream 클래스

WriteStream 클래스 요소를 컬렉션으로 쓰기 위한 접근자를 나타내는 PositionableStream의 서브클래스다. next, next:, do: 메시지는 모두 WriteStream으로 성공적으로 전송될 수 없다.


WriteStreams는 Smalltalk-80 시스템에 걸쳐 여느 객체의 문자열 설명이든 출력하거나 저장하기 위한 메서드의 일부로 사용된다. 시스템 내 각 객체는 printOn: aStream 과 storeOn: aStream 메시지에 응답할 수 있다. 이러한 메시지와 연관된 메서드들은 인자가 접근하는 컬렉션으로 요소 쓰기를 허용하는 Stream 유형의 인자에게 전송되는 메시지 시퀀스로 구성된다. 이러한 메시지로는 Character를 인자로 한 nextPut:, String 또는 Symbol을 인자로 한 nextPutAll:이 있다. 이를 예로 설명하겠다.


제 6장에서 설명하였듯 프로토콜을 출력하는 Object 객체는 printString 메시지를 포함한다. 이 메시지의 구현은 다음과 같다.

printString
    | aStream |
    aStream  WriteStream on: (String new: 16).
    self printOn: aStream.
    aStream contents


컬렉션으로 printString 메시지가 전송되면 응답은 인스턴스의 설명에 해당하는 String이 된다. 메서드는 컬렉션을 저장할 수 있는 WriteStream을 생성하고, 컬렉션으로 printOn: 메시지를 전송한 후 결과가 되는 WriteStream의 내용으로 응답한다. 어떤 객체로든 전송되는 storeString 메시지는 Object 클래스에서 유사하게 구현되는데, 차이점이라 하면 두 번째 표현식의 경우 printOn: aStream이 아니라 storeOn: aStream 메시지로 구성된다는 점을 들 수 있겠다.


컬렉션이 스스로에 대한 설명을 출력하는 일반적인 방법은 클래스명 다음에 왼쪽 괄호를 표기한 후 공백으로 각 요소를 구분한 설명, 그리고 오른쪽 괄호로 종료된다. 따라서 Set에 4개의 요소, one, two, three, four이라는 Symbols가 있을 경우 이는 Streams 상에 아래와 같이 출력된다.

Set (one two three four )


동일한 요소를 가진 OrderedCollection은 Stream상에서 아래와 같이 출력한다.

OrderedCollection (one two three four )


제 6장에 제공된 printOn:과 storeOn:의 정의에 따르면 어떤 것이든 printOn: 에 대해 적절한 설명을 제공할 수 있다는 점을 상기하되, storeOn: 이 생성한 설명을 따르면, 잘 만들어진 표현식은 평가를 할 때 그것이 설명하고자 하는 객체를 재생성(re-construct)할 수 있어야 한다.


Collection 클래스에 printOn: 에 대한 구현은 다음과 같다.

printOn: aStream
    aStream nextPutAll: self class name.
    aStream space.
    aStream nextPut: $(.
    self do:
        [ :element |
            element printOn: aStream.
            aStream space].
    aStream nextPut: $)


space 메시지는 WriteStream(aStream)으로 전송됨을 주목한다. 이 메시지를 비롯해 그 외에도 많은 메시지가 WriteStream 클래스에서 제공되어 그러한 Streams로 구분 문자를 저장하기 위한 간단한 표현식을 지원한다. 이러한 표현식으로는 다음이 있다.

문자쓰기(character writing)
cr 리턴 문자를 수신자의 다음 요소로 저장한다.
crTab 리턴 문자와 단일 탭 문자를 수신자의 다음 두 개의 문자로 저장한다.
crTab: anInteger 리턴 문자를 수신자의 다음 요소로 저장하고 탭 문자의 anInteger 개수를 그 다음으로 저장한다.
space 공백 문자를 수신자의 다음 문자로 저장한다.
tab 탭 문자를 수신자의 다음 문자로 저장한다.
WriteStream 인스턴스 프로토콜


Thus to construct the String

이름 도시
bob New York
joe Chicago
bill Rochester


따라서 두 개의 상응하는 아래의 Arrays로부터 위와 같은 String을 생성하기 위해서는

names   #(bob joe bill)
cities   #('New York' 'Chicago' 'Rochester')


아래 표현식을 평가하고,

aStream  WriteStream on: (String new: 16).
aStream nextPutAll: 'name'.
aStream tab.
aStream nextPutAll: 'city'.
aStream cr; cr.
names with: cities do:
    [ :name :city |
        aStream nextPutAll: name.
        aStream tab.
        aStream nextPutAll: city.
        aStream cr]


원하는 결과는 aStream 내용을 평가하여 얻을 수 있다.


컬렉션이 이미 존재하여 Stream 프로토콜을 이용해 컬렉션으로 추가 정보를 붙이길 원한다고 가정해보자. WriteStream 클래스는 컬렉션을 수락하고 쓰기용 위치 참조를 끝으로 설정하는 인스턴스 생성 프로토콜을 지원한다.

인스턴스 생성(instance creation)
with: aCollection aCollection 인자로 접근하는 WriteStream의 인스턴스를 응답하되 그 끝에 다음 요소를 저장하도록 위치가 조정되어야 한다.
with: aCollection from: firstIndex to: lastIndex aCollection 인자의 하위컬렉션으로 위치 firstIndex부터 lastIndex까지 접근하는 WriteStream의 인스턴스를 응답하되 하위컬렉션의 끝에 다음 요소를 저장하도록 위치가 조정되어야 한다.
WriteStream 클래스 프로토콜


따라서 아래를 포함해 header로 참조된 String이 이미 존재한다면

name city


앞의 String 예제는 아래를 이용해 생성될 것이다.

aStream  WriteStream with: header.
names with: cities do:
    [ :name :city |
        aStream nextPutAll: name.
        aStream tab.
        aStream nextPutAll: city.
        aStream cr].
aStream contents


ReadWriteStreamm 클래스

ReadWriteStreamm 클래스는 컬렉션으로 요소를 쓰고 그로부터 읽을 수 있는 접근자를 나타내는 WriteStream의 서브클래스다. 이는 위에서 제공한 바와 같이 ReadStream과 WriteStream의 프로토콜을 모두 지원한다.


생성된 요소의 스트림

컬렉션에서 스트리밍을 위한 위치 참조를 생성하는 세 가지 방법 중에 본 장의 서론에 소개한 두 번째 방법은 컬렉션의 다음 요소를 생성할 수 있는 "seed"(시드)를 명시하는 방법이다. 이러한 유형의 Stream은 요소의 쓰기는 허용하지 않고 읽기만 허용한다. 하지만 참조는 시드의 리셋을 통해 위치를 조정할 수 있다.


제 8장에 소개한 Random 클래스는 숫자를 시드로 활용하는 알고리즘을 기반으로 요소를 결정하는 Stream의 서브클래스다. Random은 next와 atEnd에 구체적인 구현을 제공한다. 컬렉션의 크기는 무한하므로 절대 끝나지 않으며 Random은 contents 메시지에 응답할 수 없다. 이는 do: 메시지에 응답할 수 있지만 메서드는 프로그래머의 의도적 간섭이 없다면 절대 끝나지 않을 것이다.


아래는 Random 클래스의 구현으로, 해당 클래스의 인스턴스 사용 예는 제 11장과 21장을 참고한다. do: 과 nextMatchFor: anObject 에 대한 구현은 Stream 클래스로부터 상속된다.

클래스명 Random
슈퍼클래스 Stream
인스턴스 변수명 seed
클래스 메서드
instance creation
    new
        self basicNew setSeed
인스턴스 메서드
testing
    atEnd
        false

accessing
    next
        | temp |
        "Lehmer's linear congruential method with modulus m = 2 raisedTo:16, a = 27181 odd, and 5 = a \\ 8, c = 13849 odd, and c/m approximately 0.21132"
        [seed  13849 + (27181 * seed) bitAnd: 8r177777.
        temp  seed / 65536.0.
        temp = 0] whileTrue.
        temp

private
    setSeed
        "For pseudo-random seed, get a time from the system clock. It is a large positive integer; just use the lower 16 bits."
        seed  Time millisecondClockValue bitAnd: 8r177777


생성된 요소의 스트림에 가능한 또 다른 예로 제 21장에 나타난 확률분포를 들 수 있다. ProbabilityDistribution 슈퍼클래스는 Stream의 서브클래스로서 구현된다. next: anInteger 메시지는 Stream에서 구현된다. 각 유형의 ProbabilityDistribution은 그것이 "read-only"(읽기 전용)인지 결정하고, 읽기 전용이라면 nextPut: 을 self shouldNotImplement로 구현한다. 제 21장에 또 다른 예인 SampleSpace 클래스는 데이터 항목의 컬렉션을 유지하고 nextPut: anObject를 컬렉션에 대한 추가로 구현한다.


외부 키가 없는 컬렉션에 대한 스트림

본 장 서론에서 컬렉션에 대한 스트리밍에 위치 참조를 유지하는 방법 세 번째로 비수치형 참조를 유지하는 방법을 언급하였다. 이는 컬렉션의 요소가 외부 키에 의해 접근될 수 없거나 그러한 접근이 효과적으로 요소를 검색하는 방법이 아닌 경우에 유용할 것이다.


LinkedList 클래스의 인스턴스에서 스트리밍은 요소들이 외부 키로서 색인에 의해 접근 가능한 예를 제공하지만 그러한 접근은 각각 연결된 요소 사슬의 하향 검색을 필요로 한다. 컬렉션에 (Link 종류) 특정 요소에 대한 참조를 유지한 후 현재 요소 nextLink를 요청함으로써 다음 요소로 접근하는 편이 더 효율적이다. LinkedList에서 읽고 쓰는 것 모두 그러한 Stream에 의해 지원 가능하다.


LinkedListStreamm이라 불리는 Stream의 하위클래스를 생성한다고 가정해보자. 각 인스턴스는 LinkedList에 대한 참조와 컬렉션 내 요소에 대한 위치 참조를 유지한다. 읽기와 쓰기가 모두 지원되므로 next, nextPut:, atEnd, contents 메시지가 구현되어야 한다. (이 4개의 메시지는 Stream 클래스에서 self subclassResponsibility로 정의됨을 주목한다.) LinkedListStreamm의 새로운 인스턴스는 on: aLinkedList 메시지를 전송하여 생성된다.

클래스명 LinkedListStream
슈퍼클래스 Stream
인스턴스 변수명 collection
currentNode
클래스 메서드
instance creation
    on: aLinkedList
        self basicNew setOn: aLinkedList
인스턴스 메서드
testing
    atEnd
        currentNode isNil

accessing
    next
        | saveCurrentNode |
        saveCurrentNode  currentNode.
        self atEnd
            ifFalse: [currentNode  currentNode nextLink].
        saveCurrentNode
    nextPut: aLink
        | index previousLink |
        self atEnd ifTrue: [collection addLast: aLink].
        index  collection indexOf: currentNode
        index = 1
            ifTrue: [collection addFirst: aLink]
            ifFalse: [previousLink  collection at: index - 1.
                previousLink nextLink: aLink].
        aLink nextLink: currentNode nextLink.
        currentNode  aLink nextLink.
        aLink

private
    setOn: aLinkedList
        collection  aLinkedList.
        currentNode  aLinkedList first


이제 이러한 새 유형의 Stream의 사용을 보이기 위해 WordLink 클래스의 인스턴스인 노드들의 LinkedList를 만드는데, WordLink 클래스는 String이나 Symbol을 저장하는 Link의 서브클래스다.

클래스명 WordLink
슈퍼클래스 Link
인스턴스 변수명 word
클래스 메서드
instance creation
    for: aString
        self new word: aString
인스턴스 메서드
accessing
    word
        word
    word: aString
        word  aString

comparing
    = aWordLink
        word = aWordLink world

printing
    printOn: aStream
        aStream nextPutAll: 'a WordLink for'.
        aStream nextPutAll: word


위를 살펴보면 #one 워드에 대한 WordLink의 인스턴스는 아래에 의해 생성됨을 확인할 수 있다.

WordLink for: #one


그 출력 문자열은 다음과 같다.

'a WordLink for one'


WordLinks의 LinkedList를 생성하고, 이러한 LinkedList로 접근하는 LinkedListStreamm을 생성한다.

list  LinkedList new.
list add: (WordLink for: #one).
list add: (WordLink for: #two).
list add: (WordLink for: #three).
list add: (WordLink for: #four).
list add: (WordLink for: #five). 
accessor  LinkedListStreamm on: list


accessor에 대한 메시지의 시퀀스 예제는 다음과 같다.

표현식 결과
accessor next a WordLink for one
accessor next a WordLink for two
accessor nextMatchFor:(WordLink for: #three) true
accessor nextPut:(WordLink for: #insert) a WordLink for insert
accessor contents LinkedList(a WordLink for one a WordLink for two a WordLink for three a WordLink for insert a WordLink for five)
accessor next a WordLink for five
accessor atEnd true


이와 비슷하게 제 11장에 제공된 Tree 클래스의 것과 같은 트리 구조체의 노드를 순회하는 일은 현재 노드의 참조를 유지하고 현재 노드의 왼쪽 트리, 노드 또는 오른쪽 트리로 접근함으로써 다음 요소로 접근하는 Stream 유형에 의해 이루어진다. 이러한 유형의 Stream은 LinkedList 보다는 구현이 조금 더 복잡한데, 왼쪽 또는 오른쪽 트리가 순회되었는지와 현재 노드의 "father"(부)로 뒤로 참조(back reference)하는지에 대한 지식을 얻어야 하기 때문이다. 트리 구조체의 순회 순서는 Stream에서 구현이 가능하며, 구조체에 하위트리가 추가된 방법은 무시한다. 따라서 Tree 클래스와 Node 클래스의 구현에 중위 순회를 사용하였다 하더라도 next와 nextPut: 메시지를 적절하게 구현하여 후위순회로 Tree를 스트리밍할 수 있다.


외부 스트림과 파일 스트림

지금까지 살펴본 Streams는 컬렉션의 요소가 표현과 무관하게 어떤 객체든 가능하다는 가정을 한다. 하지만 디스크와 같은 입출력 장치와 통신할 때는 이러한 가정이 유효하지 않다. 이런 경우 요소는 이진으로 된 바이트 크기의 요소로 저장되어 숫자, 문자열, 워드(2 바이트), 또는 바이트로서 접근을 선호할 수 있다. 따라서 크기가 다른 정보 "chunks"(청크)를 읽고 쓰기 위해서는 동질하지 않은 접근 메시지의 결합을 지원할 필요가 있다.


ExternalStream 클래스는 ReadWriteStreamm 클래스의 서브클래스다. 이것의 목표는 동질하지 않은 접근 프로토콜을 추가하는 것이다. 여기에는 접근뿐만 아니라 위치 조정을 위한 프로토콜도 포함된다.

비동질 접근 메시지(nonhomogeneous accessing)
nextNumber: n 수신자가 양의 SmallInteger 또는 LargePositiveInteger로서 접근한 컬렉션의 다음 n 바이트를 응답한다.
nextNumber: n put: v 양의 SmallInteger 또는 LargePositiveInteger에 해당하는 인자 v를 수신자가 접근하는 컬렉션의 다음 n 바이트로 저장한다. 필요 시 0 패딩 문자를 사용한다.
nextString 수신자가 접근하는 컬렉션의 다음 요소들로 이루어진 String을 응답한다.
nextStringPut: aString 수신자가 접근하는 컬렉션에 aString 인자를 저장한다.
nextWord 수신자가 접근하는 컬렉션에서 다음 두 바이트를 Integer로서 응답한다.
nextWordPut: anInteger anInteger 인자를 수신자가 접근하는 컬렉션의 다음 두 바이트로 저장한다.
비동질 위치 조정(nonhomogeneous positioning)
padTo: bsize bsize 문자의 다음 경계로 건너뛰고, 몇 개의 문자를 건너뛰었는지 응답한다.
padTo: bsize put: aCharacter 컬렉션을 패딩하기 위해 수산자가 접근하는 컬렉션으로 aCharacter 인자를 쓰는 것을 bsize 문자의 다음 경계(boundary)로 건너뛰고 몇 개의 문자가 쓰여졌는지 (패딩 문자) 응답한다.
padToNextWord 위치 참조를 짝수로 (단어 경계로) 만들고 패딩 문자가 있을 경우 그것을 응답한다.
padToNextWordPut: aCharacter 위치 참조를 짝수로 (단어 경계로) 만들고 필요 시 패딩 문자 aCharacter를 작성한다.
skipWords: nWords nWords 개의 워드 다음에 위치시킨다.
wordPosition 워드에서 현재 위치를 응답한다.
wordPosition: wp 워드에서 현재 위치를 wp 인자가 되도록 설정한다.
ExternalStream 인스턴스 프로토콜


FileStream 클래스는 ExternalStream의 서브클래스다. 외부 파일로의 접근은 모두 FileStream의 인스턴스를 이용해 이루어진다. FileStream은 큰 규모의 문자나 바이트의 시퀀스로 접근하듯이 행동하며, 시퀀스의 요소들은 Integers 또는 Characters일 것으로 가정한다. FileStream에 대한 프로토콜은 ExternalStream과 그 슈퍼클래스의 프로토콜이어야 한다. 또 프로토콜은 FileStream이 스트리밍되는 시퀀스의 상태를 설정하고 검사하기 위해 제공된다.


Smalltalk-80 시스템에서 ExternalStream과 FileStream 클래스는 파일 시스템이 생성될 수 있는 프레임워크로서 제공된다. FileStream 클래스 내 추가 프로토콜은 파일이 파일 페이지의 시퀀스인 파일 사전 또는 디렉터리로 구성된 프레임워크를 기반으로 파일 시스템이 이루어지는 것으로 간주한다. Smalltalk-80 시스템은 파일 시스템에서 이러한 구조적 부분을 나타내기 위해 FileDirectory, File, FilePage 클래스를 포함한다. FilePage는 그 File 내에서 페이지 번호로 유일하게 식별되는 데이터의 레코드다. File은 알파벳명과 일련 번호 모두 유일하게 식별되고, File을 포함하는 FileDirectory로 참조를 유지한다. 그리고 FileDirectory는 자체만으로 그것이 참조하는 "server"(서버) 또는 장치로 유일하게 식별된다. 사용자 프로그램은 주로 File이나 그 FilePages에 직접 접근하지 않으며, FileStream을 통해 바이트 또는 문자의 시퀀스로서 접근한다. 따라서 프로그래머는 아래 형태로 된 표현식을 이용해 파일에 대한 접근자로 FileStream을 생성할 수 있는데,

Disk file: 'name.smalltalk'


여기서 Disk는 FileDirectory의 인스턴스다. FileStream으로는 본 장의 프로토콜에 명시된 바와 같이 읽기 및 쓰기 메시지의 시퀀스가 전송될 수 있다.


Notes