Smalltalk80LanguageImplementationKor:Chapter 03

From 흡혈양파의 번역工房
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
제 3 장 클래스와 인스턴스

클래스와 인스턴스

객체는 Smalltalk-80 시스템의 구성요소를 표현하는데, 그러한 구성요소로는 숫자, 데이터 구조체, 프로세스, 디스크 파일, 프로세스 스케줄러, 텍스트 에디터, 컴파일러, 애플리케이션이 있다. 메시지는 Smalltalk-80 시스템의 구성요소들 간 상호작용을 나타내며, 산술, 데이터 접근, 제어 구조체, 파일 생성, 텍스트 조작, 컴파일, 애플리케이션 사용이 해당된다. 메시지는 객체의 기능을 다른 객체에서 이용 가능하게 만드는 동시 객체의 구현은 숨겨진 채로 유지한다. 앞 장에서는 객체의 기능으로 접근 시 어떻게 메시지를 사용하는지에 중점을 두고 객체와 메시지를 설명하기 위한 표현식 구문을 소개하였다. 이번 장에서는 객체의 기능이 어떻게 구현되는지 보이기 위해 메서드와 클래스를 설명하기 위한 구문을 소개하고자 한다.


모든 Smalltalk-80 객체는 클래스의 인스턴스다. 클래스의 인스턴스는 모두 동일한 메시지 인터페이스를 가지며, 클래스는 해당 인터페이스를 통해 이용 가능한 연산을 각각 어떻게 실행하는지 설명한다. 각 연산은 메서드에 의해 설명된다. 메시지의 선택자는 수신자가 실행해야 하는 연산의 타입을 결정하므로, 클래스는 그 인터페이스 안에 선택자마다 하나의 메서드를 갖고 있다. 메서드가 객체로 전송되면 수신자의 클래스에서 해당하는 메시지 타입과 연관된 메서드가 실행된다. 클래스는 그 인스턴스가 어떤 타입의 private 메모리를 갖게 될 것인지도 설명한다.


각 클래스는 그 인스턴스가 나타내는 구성요소의 타입을 설명하는 이름을 갖는다. 클래스명이 실행하는 기능은 기본적으로 두 가지인데, 인스턴스가 스스로를 간단하게 식별하도록 해주고, 표현식에서 클래스를 참조하는 방법을 제공하는 것이다. 클래스는 Smalltalk-80 시스템의 구성요소기 때문에 객체에 의해 표현된다. 클래스의 이름은 자동으로 전역적으로 공유되는 변수의 이름이 된다. 해당 변수의 값은 클래스를 나타내는 객체다. 클래스명은 공유 변수의 이름이므로 대문자로 시작해야 한다.


클래스로 메시지를 전송하면 새로운 객체가 생성된다. 대부분의 객체는 자신의 인스턴스를 새로 생성하여 new라는 단항 메시지에 응답한다. 예를 들어, 아래를 이용하면

OrderedCollection new


시스템 클래스 OrderedCollection의 인스턴스인 새로운 컬렉션을 리턴한다. 새로운 OrderedCollection은 비어 있다. 일부 클래스는 다른 메시지에 대한 응답으로 인스턴스를 생성한다. 예를 들어 인스턴스가 하루 중 시간을 나타내는 인스턴스를 가진 클래스는 Time으로, 현재 시간을 표현하는 인스턴스로 now라는 메시지에 응답한다. 인스턴스가 연중 요일(day)을 표현하는 클래스는 Date이며, 이는 today 라는 메시지에 현재 요일을 표현하는 인스턴스로 응답한다. 새로운 인스턴스가 생성되면 인스턴스 생성 메시지를 수신한 클래스의 메서드를 자동으로 공유한다.


이번 장에서는 클래스를 표현하는 두 가지 방법을 소개하는데, 하나는 인스턴스의 기능을 설명하는 것이고 다른 하나는 해당 기능의 구현을 설명하는 것이다.

  1. "Protocol description"(프로토콜 설명)은 인스턴스의 메시지 인터페이스에 메시지를 열거한다. 각 메시지에는 콤마가 따라오면서 해당 타입으로 된 메시지를 수신하면 인스턴스가 실행해야 할 연산을 설명한다.
  2. "Implementation description"(구현 설명)은 프로토콜 설명에 묘사된 기능이 어떻게 구현되는지를 보여준다. 구현 설명은 인스턴스의 private 메모리의 형태, 그리고 인스턴스가 그 연산을 어떻게 실행하는지 설명하는 메서드 집합을 제공한다.


클래스를 표현하는 세 번째 방법은 "system browser"(시스템 브라우저)라고 불리는 대화형 뷰를 이용하는 것이다. 브라우저는 프로그래밍 인터페이스의 일부로, 실행 중인 Smalltalk-80 시스템에서 사용된다. 프로토콜 설명과 구현 설명은 본 서적과 같이 비대화형 문서용으로 설계되었다. 브라우저에 관한 내용은 제 17장에서 간략하게 설명하도록 하겠다.


프로토콜 설명(Protocol Descriptions)

프로토콜 설명은 특정 클래스의 인스턴스가 이해하는 메시지를 열거한다. 각 메시지는 그 기능에 관한 주석과 함께 열거된다. 주석은 메시지가 수신되면 실행될 연산과 어떤 값이 리턴되어야 하는지에 대해 설명한다. 주석은 연산이 어떻게 실행될 것인지가 아니라 어떤 일이 발생할 것인지를 설명한다. 주석이 만일 리턴되어야 하는 값에 대해 어떠한 지시도 제공하지 않으면 값은 메시지의 수신자로 가정한다.


예를 들어, spend:for: 선택자로 하여 FinancialHistory로 전송되는 메시지에 대한 프로토콜 설명 엔트리는 다음과 같다.

spend: amount for: reason 금액 amount가 어떠한 reason에 소비되었음을 기억하라.


프로토콜 설명에서 메시지는 메시지 패턴의 형태로 설명된다. 메시지 패턴은 메시지 선택자와 인자명 집합으로 구성되는데, 인자명 집합은 선택자의 메시지가 가질 법한 인자마다 하나의 인자명으로 이루어진다.

spend: amount for: reason


가령 위의 메시지 패턴은 아래 세 개의 표현식 각각이 설명하는 메시지와 일치한다.

HouseholdFinances spend: 32.50 for: 'utilities'
HouseholdFinances spend: cost + tax for: 'food'
HouseholdFinances spend: 100 for: usualReason


인자명은 인자를 참조하기 위해 주석에서 사용된다. 위 예제의 주석에서 첫 번째 인자는 소비된 금액을, 두 번째 인자는 소비된 금액의 용도를 나타낸다.


메시지 범주(Message Categories)

비슷한 연산을 호출하는 메시지들은 범주에서 그룹화된다. 범주에는 그룹 내 메시지의 공통된 기능을 나타내는 이름을 갖고 있다. 예를 들어, FinancialHistory로 전송되는 메시지는 transaction recording, inquiries, initialization이라는 세 개의 범주로 그룹화된다. 이러한 범주화의 목적은 사용자가 프로토콜을 더 수월하게 읽을 수 있도록 만드는 데에 있으며, 클래스의 연산에는 영향을 미치지 않는다.


FinancialHistory에 대한 전체적인 프로토콜 설명은 아래에 표시하겠다.

transaction recording
receive: amount from: source 금액 amount가 source로부터 수신되었음을 기억하라.
spend:amount for: reason 금액 amount가 reason의 용도로 사용되었음을 기억하라.
inquiries
cashOnHand 현재 수중에 있는 총 금액을 응답하라.
totalReceivedFrom: source 지금까지 source로부터 받은 총 금액을 응답하라.
totalSpentFor: reason 지금까지 reason의 용도로 소비한 총 금액을 응답하라.
initialization
initialBalance: amount amount를 현재 수중에 있는 금액을 기준해서 재정 내역을 시작하라.
FinancialHistory protocol


프로토콜 설명은 프로그래머가 클래스의 인스턴스를 사용하는 방법을 이해하기에 충분한 정보를 제공한다. 위의 프로토콜 설명을 통해 우리는 FinancialHistory의 어떤 인스턴스든 receive:from:, spend:for:, cashOnHand, totalReceivedFrom:, totalSpentFor:, initialBalance:를 선택자로 하는 메시지에 응답해야 함을 알고 있다. FinancialHistory의 인스턴스를 먼저 생성하면 그 변수에 대한 값을 설정하기 위해선 initialBalance: 메시지가 인스턴스로 전송되어야 한다고 짐작할 수 있다.


구현 설명(Implementation Descriptions)

구현 설명은 세 가지 부분으로 나뉜다.

  1. 클래스명
  2. 인스턴스에 이용 가능한 변수의 선언
  3. 인스턴스가 메시지에 응답하기 위해 사용하는 메서드


다음으로는 FinancialHistory에 대한 전체적인 구현 설명의 예를 소개하겠다. 구현 설명에서 메서드는 프로토콜 설명에 사용된 것과 동일한 범주로 나뉜다. 대화형 시스템 브라우저에서 범주를 이용해 클래스 설명의 부분으로 접근하기 위한 계층적 쿼리 경로를 제공한다. 구현 설명의 다양한 부분들을 구분하기 위해 사용되는 특수 문자는 없다. 다른 부분들은 문자 글꼴과 강조 표시를 변경하여 표시된다. 대화형 시스템 브라우저에서 부분들은 서로 따로 저장되고, 시스템 브라우저는 그러한 부분들로 접근하기 위해 구조화된 에디터를 제공한다.

클래스명 FinancialHistory
인스턴스 변수명 cashOnHand
incomes
expenditures
인스턴스 메서드
transaction recording
receive: amount from: source
    incomes at: source
        put: (self totalReceivedFrom: source) + amount.
    cashOnHand   cashOnHand + amount
spend: amount for: reason
    expenditures at: reason
        put: (self totalSpentFor: reason) + amount.
    cashOnHand   cashOnHand - amount

inquiries
cashOnHand
     cashOnHand
totalReceivedFrom: source
    (incomes includesKey: source)
        ifTrue: [ incomes at: source]
        ifFalse: [ 0]
totalSpentFor: reason
    (expenditures includesKey: reason)
        ifTrue: [ expenditures at: reason]
        ifFalse: [ 0]

initialization
initialBalance: amount
    cashOnHand   amount.
    incomes   Dictionary new.
    expenditures   Dictionary new


이러한 구현 설명은 본 서적의 첫표지 안에 FinancialHistory로 제시된 내용과는 다르다. 첫표지에 안에 실린 설명에는 제 5장에서 설명하게 될 "class methods" (클래스 메서드)라는 부분이 추가로 포함되어 있지만 현재로선 빠져있고, 초기화 메서드 또한 생략된 모습이다.


변수 선언(Variable Declarations)

클래스 내 메서드들은 다섯 가지 유형의 변수로 접근성할 수 있다. 이러한 유형의 변수들은 얼마나 널리 이용 가능한지(범쥐)와 얼마나 오래 지속되는지에 따라 차이가 있다.


단일 객체에서만 이용 가능한 private 변수에는 두 가지 종류가 있다.

1. "Instance variables" (인스턴스 변수)는 객체의 전체 수명에 걸쳐 존재한다.
2. "Temporary variables" (임시 변수)는 특정 활동을 위해 생성되며, 활동의 지속 기간에만 이용 가능하다.


인스턴스 변수는 객체의 현재 상태를 표현한다. 임시 변수는 일부 활동을 실행하는 데에 필요한 일시적 상태를 표현한다. 임시 변수는 일반적으로 메서드의 단일 실행과 연관되는데, 메시지가 메서드의 실행을 야기하면 생성되었다가 메서드가 값을 리턴하어 완료되면 없어진다.


하나 이상의 객체에서 접근할 수 있는 변수가 세 가지 유형이 더 있다. 이러한 변수들은 얼마나 널리 공유되는지에 따라 구별된다.

3. "Class variables" (클래스 변수)는 단일 클래스의 모든 인스턴스에서 공유한다.
4. "Global variables" (전역 변수)는 모든 클래스의 모든 인스턴스에서 (즉, 모든 객체가) 공유한다.
5. "Pool variables" (풀 변수)는 시스템 내 클래스의 하위집합의 인스턴스에서 공유한다.


시스템에서 대다수의 공유 변수는 클래스 변수이거나 전역 변수에 해당한다. 전역 변수의 대다수는 시스템 내 클래스를 참조한다. FinancialHistory의 인스턴스인 HouseholdFinances가 앞 장의 여러 예제에서 사용된 바 있다. 우리는 HouseholdFinances를 마치 전역 변수명으로 정의된 것처럼 사용했다. 전역 변수는 다른 객체의 일부가 아닌 객체를 참조하는 데 사용된다.


공유 변수(3-5)의 이름은 대문자로 시작되며 private 변수(1-2)의 이름은 그렇지 않음을 상기해보자. 공유 변수의 값은 그 이름이 표시되는 메서드를 어떤 인스턴스가 사용하는지와 무관할 것이다. 인스턴스 변수와 임시 변수의 값은 메시지를 수신한 메서드를 사용하는 인스턴스에 따라 좌우될 것이다.


인스턴스 변수(Instance Variables)

인스턴스 변수에는 명명된 타입과 색인된 타입, 두 가지가 있다. 둘은 선언되는 방법과 접근하는 방법에 차이가 있다. 클래스는 명명된 인스턴스 변수만 가질 수도, 색인된 변수만 가질 수도, 혹은 둘을 조금씩 포함할 수도 있다.


❏ "Named Instance Variables" (명명된 인스턴스 변수). 구현 설명은 각 인스턴스를 구성하는 인스턴스 변수에 대한 이름 집합을 포함한다. 각 인스턴스에는 인스턴스 변수명에 해당하는 하나의 변수만 있다. FinancialHistory의 구현 설명에서 변수 선언은 세 개의 인스턴스 변수명을 명시한다.

인스턴스 변수명 cashOnHand
incomes
expenditures


FinancialHistory 의 인스턴스는 총 소비금액과 다양한 용도로 받은 금액을 저장하기 위해 두 개의 사전을 사용하고, 수중에 현금 사용을 기록하기 위해 또 다른 변수를 사용한다.

  • expenditures는 사용 용도와 소비된 금액을 연관시키는 사전을 나타낸다.
  • incomes는 수입 출처와 받은 금액을 연관시키는 사전을 나타낸다.
  • cashOnHand는 이용 가능한 금액을 표현하는 숫자를 나타낸다.


클래스의 메서드에서 표현식이 incomes, expenditures, cashOnHand 라는 변수명들 중 하나를 사용하면 이러한 표현식은 메시지를 수신한 인스턴스에서 그에 상응하는 인스턴스 변수의 값을 참조한다.


클래스로 메시지를 전송함으로써 새로운 인스턴스가 생성되면 새로운 인스턴스 변수 집합을 갖는다. 인스턴스 변수는 인스턴스 생성 메시지와 연관된 메서드에 명시된 바와 같이 초기화된다. 기본 초기화 메서드는 각 인스턴스 변수에게 nil 값을 제공한다.


예를 들어, 앞의 예제에서 HouseholdFinances로 전송되는 메시지가 효과를 보이기 위해서는 아래와 같은 표현식이 평가되어야 한다.

HouseholdFinances   FinancialHistory new initialBalance: 350


FinancialHistory는 세 개의 인스턴스 변수가 모두 nil을 참조하는 새로운 객체를 생성한다. 해당하는 새 인스턴스로 전송되는 initialBalance: 메시지는 3개의 인스턴스 변수로 좀 더 적절한 초기값을 제공한다.


❏ "Indexed Instance Variables" (색인된 인스턴스 변수). 일부 클래스의 인스턴스는 이름으로 접근할 수 없는 인스턴스 변수를 가질 수도 있다. 이러한 변수를 색인된 인스턴스 변수라고 부른다. 색인된 인스턴스 변수는 이름으로 참조되는 대신 색인이라 불리는 정수를 인자로 포함하는 메시지에 의해 참조된다. 색인(indexing)은 연관의 형태기 때문에 두 개의 인덱싱 메시지가 사전으로 전송되는 연관 메시지와 동일한 선택자를 가지는데, 이 메시지는 at: 와 at:put: 이다. 예를 들어, Array의 인스턴스는 색인 변수를 갖는다. names가 Array의 인스턴스일 경우

names at: 1


위의 표현식은 첫 색인된 인스턴스 변수의 값을 리턴한다.

names at: 4 put: 'Adele'


그리고 위의 표현식은 names의 네 번째 색인된 인스턴스 변수의 값으로 'Adele'이란 문자열을 저장한다. 합당한 색인은 1부터 인스턴스 내에 색인된 변수의 개수까지 이어진다.


클래스의 인스턴스가 색인된 인스턴스 변수를 가질 경우, 그 변수 선언은 indexed instance variables라는 행을 포함할 것이다. 가령 Array 시스템 클래스에 대한 구현 설명의 일부는 다음과 같다.

class name Array
indexed instance variables


색인된 인스턴스 변수를 허용하는 클래스의 각 인스턴스는 그러한 변수를 다른 수만큼 가질 수 있다. FinancialHistory의 인스턴스는 모두 3개의 인스턴스 변수를 갖지만, Array의 인스턴스는 인스턴스 변수를 원하는 수만큼 가질 수 있다.


인스턴스가 색인된 인스턴스 변수를 가진 클래스 또한 명명된 인스턴스 변수를 가질 수 있다. 그러한 클래스의 모든 인스턴스는 동일한 수의 명명된 인스턴스 변수를 가지겠지만 색인된 변수의 개수에는 차이가 나기도 한다. 가령 요소가 정렬된 컬렉션을 나타내는 시스템 클래스 OrderedCollection이 내용을 보유하기 위해 색인된 인스턴스 변수를 갖고 있는 것을 예로 들 수 있겠다. OrderedCollection은 요소를 저장하는 데에 현재 사용 중인 공간보다 많은 공간을 가질 수도 있다. 두 개의 명명된 인스턴스 변수는 내용의 첫 번째와 마지막 요소의 색인을 기억한다.

클래스 이름 OrderedCollection
인스턴스 변수명 firstIndex
lastIndex
색인된 인스턴스 변수


OrderedCollection의 모든 인스턴스는 두 개의 명명된 변수를 갖겠지만 그 중 하나는 5개의 색인된 인스턴스 변수를, 다른 하나는 15개, 또 하나는 18개를 가질 수도 있다.


FinancialHistory의 인스턴스 중에서 명명된 인스턴스 변수는 변수 값으로의 접근이 인스턴스에 의해 제어된다는 의미에서 private하다고 말할 수 있다. 클래스는 인스턴스 변수로 직접 접근을 제공하는 메시지를 포함하기도 하고 포함하지 않아도 된다. 이런 의미에서 색인된 인스턴스 변수는 private하지 않은데, at:과 at:put: 을 선택자로 하여 메시지를 전송하면 변수의 값으로 직접 접근을 이용할 수 있기 때문이다. 이러한 메시지는 색인된 인스턴스 변수로 접근하는 유일한 방법이 아니기 때문에 제공되어야만 한다.


색인된 인스턴스 변수가 있는 클래스는 일반적으로 사용하는 메시지 new 대신 new: 라는 메시지를 이용해 새 인스턴스를 생성한다. new: 의 인자는 제공되어야 하는 색인된 변수의 개수를 알려준다.

list   Array new: 10


위의 표현식은 10개 요소의 Array를 생성하는데, 각 요소는 처음에는 특수 객체 nil에 해당한다. 인스턴스의 색인된 인스턴스 변수의 개수는 size 메시지를 전송하여 찾을 수 있다. 이 예제에서 size 메시지에 대한 응답은 정수 10이 된다.

list size


아래 표현식을 순서대로 평가하면

list   Array new: 3.
list at: 1 put: 'one'.
list at: 2 put: 'two'.
list at: 3 put: 'three'


아래의 표현식 하나를 평가하는 것과 동일하다.

list   #('one' 'two' 'three')


공유 변수(Shared Variables)

하나 이상의 객체가 공유하는 변수는 "pools"(풀)이라는 그룹으로 되어 있다. 각 클래스에는 인스턴스에서 접근할 수 있는 변수를 가진 풀이 두 개 또는 그 이상이 존재한다. 하나의 풀은 모든 클래스에서 공유되고 전역 변수를 포함하는데, 이러한 풀은 Smalltalk라고 명명된다. 각 클래스는 또 그 인스턴스에서만 이용 할 수 있고 클래스 변수를 포함하는 풀을 가진다.


이러한 두 가지 풀 외에도 클래스는 여러 클래스에서 공유하는 몇 가지 특별한 용도의 다른 풀로도 접근할 수 있다. 예를 들자면, 시스템에는 텍스트 정보를 표현하는 클래스가 몇 가지 있는데 이러한 클래스들은 캐리지 리턴, 탭 또는 공백(스페이스)과 같이 시각적으로 쉽게 표시할 수 없는 문자에 대해 ASCII 문자 코드를 공유할 필요가 있다. 이러한 숫자들은 텍스트 표시와 텍스트 편집을 구현하는 클래스들이 공유하는 TextConstants라는 풀에 변수로서 포함된다.


FinancialHistory가 SalesTaxRate라는 클래스 변수를 갖고 있고 FinancialConstants라는 이름의 풀 사전을 공유한다면 선언은 아래와 같이 표시될 것이다.

인스턴스 변수명 cashOnHand
incomes
expenditures
클래스 변수명 SalesTaxRate
공유 풀 FinancialConstants


SalesTaxRate는 클래스 변수명이므로 클래스 내 어떤 메서드에서도 사용이 가능하다. 반면 FinancialConstants는 풀의 이름으로, 표현식에 사용할 수 있는 것은 풀 안에 있는 변수들이다.


변수를 전역적으로 (사용자의 대화형 시스템과 모든 클래스에 알려진) 선언하기 위해서는 변수명이 Smalltalk라는 사전에 키로서 삽입되어야 한다. 예를 들어 AllHistories를 전역적으로 만들기 위해서는 아래의 표현식을 평가한다.

Smalltalk at: #AllHistories put: nil


그리고 할당문을 이용해 AllHistories의 값을 설정한다.


메서드(Methods)

메서드는 객체가 그 연산을 어떻게 실행할 것인지를 설명한다. 메서드는 마침표로 구분된 표현식의 시퀀스와 메시지 패턴으로 구성된다. 아래 표시된 메서드 예제는 소비를 알려주는 메시지에 대한 FinancialHistory의 응답을 설명한다.

spend: amount for: reason
    expenditures at: reason
        put: (self totalSpentFor: reason) + amount.
    cashOnHand   cashOnHand - amount


메시지 패턴 spend: amount for: reason은 해당 메서드가 spend:for: 를 선택자로 하는 모든 메시지에 대한 응답으로 사용될 것을 나타낸다. 해당 메서드의 본체(body)에 포함된 첫 번째 표현식은 표시된 용도로 이미 소비된 금액에 새로운 금액을 추가한다. 그리고 두 번째 표현식은 cashOnHand 의 값에서 새로운 금액만큼 감소시키는 할당이다.


인자 이름(Argument Names)

본장 앞 부분에서 메시지 패턴을 소개하였다. 메시지 패턴은 메시지 선택자와 인자명 집합을 포함하는데, 인자명은 해당 선택자의 메시지가 가질 법한 인자마다 하나씩 해당한다. 메시지 패턴은 동일한 선택자를 가진 메시지라면 무엇이든 일치한다. 클래스는 메시지 패턴에 주어진 선택자의 메서드를 하나만 가질 것이다. 메시지가 전송되면 수신자의 클래스로부터 매칭하는 메시지 패턴의 메서드가 선택된다. 선택된 메서드 내에 표현식이 하나씩 평가된다. 모든 표현식이 평가되고 나면 메시지의 전송자에게 값이 리턴된다.


메서드의 메시지 패턴에서 발견되는 인자명은 의사 변수명으로서 실제 메시지의 인자를 참조한다. 위에 표시한 메서드가 아래 표현식으로 호출될 경우,

HouseholdFinances spend: 30.45 for: 'food'


의사 변수명 amount는 숫자 30.45를 참조하고, 의사 변수명 reason은 메서드 내 표현식이 평가되는 동안 'food' 문자열을 참조할 것이다. 아래 표현식을 이용해 동일한 메서드가 호출될 경우,

HouseholdFinances spend: cost + tax for: 'food'


cost 로 + tax 가 전송되고, 그것이 리턴한 값은 메서드에서 amount로 참조될 것이다. cost가 100을 참조하고 tax가 6.5를 참조했다면 amount의 값은 106.5가 될 것이다.


인자명은 의사 변수명이기 때문에 변수명과 같은 값으로 접근하는 데에 사용할 수 있지만 그들의 값은 할당에 의해 변경될 수 없다. spend:fot: 에 대한 메서드에서 아래와 같은 형식의 문은 구문적으로 합당하지 않은데,

amount   amount * taxRate


그 이유는 amount의 값이 재할당될 수 없기 때문이다.


반환값(Returning Values)

spend:for: 에 대한 메서드는 메시지의 값이 무엇이 되어야 하는지를 명시하지 않는다. 따라서 기본값인 수신자 자체가 리턴될 것이다. 다른 값이 명시된다면 하나 또는 그 이상의 리턴 표현식이 메서드에 포함된다. 어떤 표현식이든 윗방향 화살표(↑)를 앞에 붙이면 리턴 표현식이 될 수 있다. 변수의 값은 아래와 같이 리턴될 수 있다.

 cashOnHand


다른 메시지의 값은 아래와 같이 리턴될 수 있다.

 expenditures at: reason


그리고 리터럴 객체는 아래와 같이 리턴이 가능하다.

 0


심지어 할당문도 리턴 표현식으로 바뀔 수 있다.

 initialIndex   0


할당문이 먼저 실행된다. 그리고 나서 변수의 새로운 값이 리턴된다.


리턴 표현식의 사용에 대한 예로 totalSpentFor: 의 구현을 소개하겠다.

totalSpentFor: reason
    (expenditures includesKey: reason)
        ifTrue: [ expenditures at: reason]
        ifFalse: [ 0]


해당 메서드는 단일 조건 표현식으로 구성된다. reason 이라는 지출이 expenditures에 위치한다면 연관된 값이 리턴되고, 그렇지 않으면 0이 리턴된다.


의사 변수 self(The Pseudo-variable self)

메시지의 인자를 참조하기 위해 사용되는 의사 변수 외에도 모든 메서드가 메시지 수신자 자체를 참조하는 의사 변수 self로 접근성을 가진다. 예를 들어, spend:for: 에 대한 메서드에서는 spend:for: 메시지의 수신자로 totalSpentFor: 메시지가 전송된다.

spend: amount for: reason
    expenditures at: reason
        put: (self totalSpentFor: reason) + amount.
    cashOnHand   cashOnHand - amount


이 메서드가 실행되면 가장 먼저 spend:for: 를 수신한 것과 동일한 객체(self)로 totalSpentFor: 가 전송된다. 해당 메시지의 결과로 + amount 메시지가 전송되고, 이 메시지의 결과는 at:put: 에 두 번째 인자로 사용된다.


의사 변수 self는 재귀 함수를 구현하는 데에 사용할 수 있다. 가령 factorial 메시지는 적절한 함수를 계산하기 위해 정수에서 이해된다. factorial과 연관된 메서드는 다음과 같다.

factorial
    self = 0 ifTrue: [ 1].
    self < 0
        ifTrue: [self error: 'factorial invalid']
        ifFalse: [ self * (self - 1) factorial]


수신자는 Integer에 해당한다. 첫 번째 표현식은 수신자가 0인지 테스트하고, 0일 경우 1을 리턴한다. 두 번째 표현식은 수신자의 부호를 확인하는데, 0보다 작으면 프로그래머가 오류를 통지 받아야 하기 때문이다 (모든 객체는 error: 메시지에 오류가 발생했다는 보고서로 응답한다). 수신자가 0보다 크면 리턴되어야 할 값은 다음과 같다.

self * (self - 1) factorial


리턴된 값은 수신자부터 시작해 수신자보다 1 작은 수까지 모두 곱한 값이다.


임시 변수(Temporary Variables)

인자명과 self는 메서드를 한 번 실행하는 동안에만 이용 가능하다. 이러한 의사 변수명에 더해 메서드는 그 실행 동안 사용할 수 있는 변수들을 몇 가지 얻을 수 있다. 이를 임시 변수라고 부른다. 임시 변수는 메서드의 표현식과 메시지 패턴 사이에 임시 변수 선언을 포함시킴으로써 표시된다. 임시 선언은 수직 막대 사이에 위치한 변수명 집합으로 구성된다. spend:for: 에 대한 메서드는 앞의 지출을 보유하기 위해 임시 변수를 사용하도록 재작성될 수 있다.

spend: amount for: reason
    | previousExpenditures |
    previousExpenditures - self totalSpentFor: reason.
    expenditures at: reason
        put: previousExpenditures + amount.
    cashOnHand   cashOnHand - amount


임시 변수의 값은 메서드 내의 문으로만 접근할 수 있으며, 메서드가 실행을 완료하면 잊혀진다. 모든 임시 변수는 처음에 nil을 참조한다.


대화형 Smalltalk-80 시스템에서 프로그래머는 임시 변수를 활용하는 알고리즘을 테스트할 수 있다. 테스트는 즉각적인 평가 지속기간 동안에만 변수를 선언하기 위해 수직 막대 표기법을 사용하여 실행할 수 있다.


테스트할 표현식이 list 변수로의 참조를 포함한다고 가정해보자. 변수 list가 선언되지 않은 상황에서 표현식을 평가하려고 하면 구문 오류 메시지가 생성될 것이다. 대신 프로그래머는 표현식 앞에 선언 | list |를 붙여 list를 임시 변수로 선언할 수 있다. 표현식은 메서드 구문과 마찬가지로 마침표로 구분된다.

| list |
list   Array new: 3.
list at: 1 put: 'one'.
list at: 2 put: 'four'.
list printString


프로그래머는 선언과 표현식의 5개 행 모두를 선택하여 평가를 요청한다. list 변수는 선택을 한 번 실행하는 동안에만 이용할 수 있다.


프리미티브 메서드(Primitive Methods)

객체는 메시지를 수신하면 다른 메시지들을 전송하는 것이 보통인데, 그렇다면 실제로 어떤 일이 발생하는 걸까? 객체는 메시지를 수신하면 그 인스턴스 변수의 값을 변경할 수도 있는데, 값이 변경되어야만 사실상 "무언가 발생하고" 있다는 뜻이 성립된다. 하지만 이도 부족해 보인다. 사실상 부족한 것도 맞다. 시스템 내 모든 행위는 메시지에 의해 호출되는 것이 맞지만 모든 메시지가 Smalltalk-80 메서드를 실행하여 응답되는 것은 아니다. Smalltalk-80 가상 머신이 실행 방법을 아는 프리미티브 메서드의 수는 약 100개에 달한다. 프리미티브를 호출하는 메시지의 예로는 소수로 전송되는 + 메시지, 색인된 인스턴스 변수를 가진 객체로 전송되는 at: 메시지, 그리고 클래스로 전송되는 new와 new: 메시지가 있다. 3이 +4 메시지를 얻으면 Smalltalk-80 메서드를 실행하지 않는다. 대신 프리미티브 메서드가 메시지의 값으로 7을 리턴한다. 프리미티브 메서드의 전체 집합은 본 서적에서 가상 머신을 설명하는 제 4부에 포함되어 있다. 프리미티브 메서드로 구현되는 메서드는 아래와 같은 형태의 표현식으로 시작된다.

<primitive #>


이러한 표현식에서 # 은 프리미티브 메서드가 따라온다는 의미의 정수다. 프리미티브 메서드가 올바로 실행되지 못하면 실행은 Smalltalk-80 메서드에서 계속된다. <primitive #> 표현식 다음에는 실패 상황을 처리하는 Smalltalk-80 표현식이 따라온다.


용어 정리

클래스(class) 유사한 객체 집합의 구현을 설명하는 객체.
인스턴스(instance) 클래스가 설명하는 객체들 중 하나로, 메모리를 가지며 메시지에 응답한다.
인스턴스 변수(instance variable) 객체의 전체 수명 주기 동안 단일 객체에서 이용 가능한 변수. 인스턴스 변수는 명명 또는 색인이 가능하다.
프로토콜 설명(protocol description) 클래스의 인스턴스의 public 메시지 프로토콜과 관련된 클래스의 설명.
구현 설명(implementation description) 클래스의 인스턴스의 private 메서드와, 인스턴스가 그 연산을 어떻게 실행하는지 설명하는 메서드 집합과 관련된 클래스의 설명.
메시지 패턴(message pattern) 메시지 선택자와 인자명 집합으로, 인자명은 해당 선택자로 된 메시지가 가져야 하는 인자마다 하나씩 부여된다.
임시 변수(temporary variable) 특정 활동에 생성된 변수로, 해당 활동이 지속되는 기간에만 이용 가능하다.
클래스 변수(class variable) 단일 클래스의 모든 인스턴스가 공유하는 변수.
전역 변수(global variable) 모든 클래스의 모든 인스턴스가 공유하는 변수.
풀 변수(pool variable) 클래스 집합의 인스턴스가 공유하는 변수.
Smalltalk 전역 변수를 포함하는 모든 클래스가 공유하는 풀.
메서드(method) 객체의 연산 중 하나를 어떻게 실행하는지 설명하는 절차로, 메시지 패턴, 임시 변수 선언, 표현식의 시퀀스로 구성된다. 메서드가 발견된 클래스의 인스턴스로 해당 메시지 패턴에 일치하는 메시지가 전송되면 메서드가 실행된다.
인자명(argument name) 메서드에서 이용 가능한 의사 변수명으로, 해당 메서드의 실행 기간 중에만 이용할 수 있으며, 인자명의 값은 메서드를 호출한 메서드의 인자에 해당한다.
메서드에서 사용될 경우 다음 표현식의 값이 메서드의 값이 될 것을 의미한다.
self 메시지의 수신자를 참조하는 의사 변수.
메시지 범주(message category) 클래스 설명 내 메서드의 그룹.
프리미티브 메서드(primitive method) Smalltalk-80 가상 머신이 직접 실행하는 연산으로, Smalltalk-80 표현식의 시퀀스로 설명되지는 않는다.


Notes