SqueakByExample:5.3

From 흡혈양파의 번역工房
Jump to navigation Jump to search

모든 객체는 클래스의 인스턴스입니다

모든 객체는 클래스를 가지고 있으며, 스몰토크는 객체에 class라는 메시지를 보내어 원하는 객체의 클래스를 알아낼 수 있습니다.

1 class ⇒ SmallInteger
20 factorial class ⇒ LargePositiveInteger
'hello' class ⇒ ByteString
#(1 2 3) class ⇒ Array
(4@5) class ⇒ Point
Object new class ⇒ Object


클래스는 인스턴스 변수를 통해 클래스 스스로에 대한 인스턴스의 구조를 정의하고, 메서드를 통해 클래스 인스턴스의 동작을 정의합니다. 메서드는 다른곳에서 호출될 수 있는 고유한 이름을 가진 셀렉터로서 클래스안에 있습니다.

클래스는 객체이고, 모든 객체는 클래스의 인스턴스이기 때문에, 클래스는 반드시 클래스의 인스턴스가 되어야 한다는 규칙이 따라옵니다. 클래스가 인스턴스인 클래스를 메타 클래스라고 합니다. 클래스를 만들 때마다 언제든지 시스템은 메타 클래스를 자동으로 만들어줍니다. 메타 클래스는 자신의 인스턴스가 되는 클래스의 구조와 동작을 정의합니다. 99%의 경우, 메타 클래스에 대해 생각할 필요가 없으며, 기꺼이 무시해도 됩니다. (12장에서 메타클래스를 좀 더 자세히 살펴보겠습니다.)


인스턴스 변수

스몰토크에서 인스턴수 변수는 해당 인스턴스 내부(private)에서만 사용합니다. 스몰토크와는 반대로 인스턴스 변수에 대한 외부접근을 허용하는 Java와 C++등에서는(이러한 변수를 필드 또는 멤버변수라고 합니다) 같은 클래스에서 만들어진 다른 어떤 인스턴스에서도 인스턴스 변수에 접근이 가능합니다. Java와 C++에서는 객체의 캡슐화 범위를 클래스까지로 보지만 스몰토크에서는 인스턴스까지를 캡슐화 범위로 생각해야 합니다.

스몰토크에서, 동일한 클래스로 만든 두 개의 인스턴스는, 클래스가 "접근자 메서드"를 정의하지 않으면, 서로 다른 인스턴스의 인스턴스 변수에 접근할 수 없습니다. 스퀵은 자신외의 그 어떤 다른 객체도 인스턴스 변수로 바로 접근하는 기능을 제공되는 문법을 언어에서 지원하지 않습니다. (사실 reflection(반영적)이라고 하는 기법은 메타 프로그래밍처럼 다른 객체에서 인스턴스 변수의 값을 요청할 수 있는 방법을 제공합니다: 메타 프로그래밍은 객체 인스펙터처럼 다른 객체 내부를 살펴볼 수 있는 도구를 위해 만들어졌습니다.)

인스턴스 변수는 인스턴스를 생성한 클래스에서 정의된 인스턴스의 메서드 또는 인스턴스를 정의한 클래스의 하위클래스에서 정의한 메서드 중 어떤메서드를 사용해도 접근이 가능합니다. 이것은 스몰토크 인스턴스 변수들이, C++과 Java에서, 보호(Protected)된 변수들과 비슷하다는 뜻이 됩니다. 그럼에도 불구하고, 스몰토크에서는 이 인스턴스 변수들을 개별적(private,혹은 전용)이라고 부르는것을 선호하는데, 이유는 서브클래스에서부터 직접 인스턴스 변수에 접근하는 형식을, 좋지 않게 생각하기 때문입니다.


Example

Method Point>>dist:(메서드 5.1) 는 수신자와 다른 점 사이의 거리를 계산합니다. 수신자의 인스턴스 변수인 x와 y 두개는 메서드 안에서 직접 접근이 가능합니다. 그러나 자신 외의 다른 점의 인스턴스 변수들은 aPoint로 들어온 객체의 x와 y 메서드에 메시지를 보내서 값을 받는 방법으로 변수에 접근해야 합니다.


메서드 5.1: 두 점 사이의 각도

Point>>dist: aPoint
  "aPoint와 수신자 사이의 거리를 계산합니다."
  | dx dy |
  dx := aPoint x - x.
  dy := aPoint y - y.
   ((dx * dx) + (dy * dy)) sqrt
  1@1 dist: 4@5      5.0


클래스 기반 캡슐화 보다 인스턴스 기반 캡슐화를 선호하는 이유는, 인스턴스로 생성된 객체를 사용하려 할 때 추상화된 인스턴스의 사용법만 동일하다면 인스턴스 내부의 구현을 다르게 만들 수 있기 때문입니다. 예를 들면, method point>>dist: 메서드는 인자 aPoint가 수신자와 동일한 클래스의 인스턴스인지의 여부를 알 필요도 없고 신경쓸 필요도 없습니다. 인자 객체가 메시지 x와 y에 반응된다면, aPoint라는 객체는 끝 좌표가 될수도 있고, 데이터베이스에서 얻어진 데이터가 될 수도 있고 분산된 시스템의 다른컴퓨터의 데이터가 될 수도 있습니다. 메시지 x와 y에서 응답을 얻을 수 있을때까지, 메서드 5.1의 코드는 잘 작동합니다.


메서드

모든 메서드는 public 입니다[1]. 메서드들은 목적에 따라 프로토콜을 이용해서 분류됩니다. 몇가지 공통으로 쓰이는 프로토콜 이름은 관례에 따라 만들어 졌습니다. 모든 접근자 메서드에 대한 접근과, 객체에 적용하는 초기화 상태를 만들기 위한 initialization 프로토콜이 이런 관례의 예가 되겠습니다. private 프로토콜은 가끔 외부에서 보면 안되는 메서드를 그룹으로 만들 때 사용합니다. 그렇다고 해서 private메서드로 구현된 셀렉터에 메시지를 보내는것을 막는 방법은 없습니다.[2]

메서드는 객체의 모든 인스턴스 변수에 접근할 수 있습니다. 일부 스몰토크 개발자들은 오직 접근자accessor만 인스턴스 변수에 접근할 수 있도록 하는 방식을 좋아합니다. 이런 방식을 활용하는 경우는 있지만, 사용자의 클래스 인터페이스를 어수선하게 만들며, 외부에서 인스턴스 또는 클래스에 대한 private의 상태를 알 수 있는 가능성이 됩니다.


인스턴스 관점과 클래스 관점

클래스는 곧 객체이므로, 클래스는 인스턴스 변수와 메서드를 보유할 수 있습니다. 이런것들을 클래스 인스턴스 변수 와 클래스 메서드라고 하지만, 보통의 인스턴스 변수, 메서드와는 실제로 차이가 있는건 아닙니다: 클래스 인스턴스 변수들은 단지 메타클래스로 정의되는 인스턴스 변수들이며, 클래스 메서드는 단지 메타클래스에 의해 정의되는 메서드 일 뿐입니다.

클래스와 그 클래스의 메타클래스는 두 개의 구별된 클래스 이며, 심지어 클래스는 메타클래스의 인스턴스이기도 합니다. 하지만, 이런 내용은 프로그래머에게 크게 상관은 없습니다: 당신은 객체와 클래스를 만드는것에 신경쓰면 됩니다.

그림 5.1: 클래스와 그 자체의 메타 클래스(metaclass)를 검색하기


이렇게 클래스는 메타클래스의 인스턴스 라는 특성때문에, 그림 5.1에 보이는 것처럼 System Browser 에서는 클래스와 메타클래스를 "instance" side 그리고 "class" side 의 양면성을 지닌 한몸처럼 탐색할 수 있습니다. 클래스 color 등을 검색하기 위해 instance 버튼을 클릭하면, blue와 같은 color의 인스턴스에 메시지를 보냈을때, 실행되는 메서드를 탐색하면 됩니다. class 버튼을 누르면, 클래스 Color class 를 탐색합니다. 이런 방법으로 당신은 color 클래스에 메시지가 실제로 보내질 때, 실행되는 메서드를 볼 수 있습니다. 예를 들어, Color blue는 클래스 color에 메시지를 보냅니다. 그렇게 되면, 인스턴스 관점이 아닌, color의 클래스 관점에서 정의된 메서드 blue 를 찾게됩니다.

aColor := Color blue.   "Class side method blue"
aColor ⇒ Color blue  
aColor red ⇒ 0.0 "Instance side accessor method red"
aColor blue ⇒ 1.0 "Instance side accessor method blue"


클래스는 System Browser의 인스턴스 관점에서 언급한 템플릿에 내용을 채워 정의합니다. 이렇게 내용이 채워진 템플릿을 Accept 하게되면, 시스템은 사용자가 정의한 클래스뿐만 아니라, 이에 대응하는 메타클래스도 같이 만들게 됩니다. 또한 System Browser의 class 버튼을 클릭하여 메타클래스를 검색하는것도 가능합니다. 메타클래스 생성 탬플릿에서 인스턴스 변수이름목록 외에 당신이 편집할 수 있는건 없습니다.

클래스를 만들고 나면, instance 버튼을 클릭하면, 클래스의 인스턴스(와 클래스의 서브클래스)가 소유할 메서드를 편집하고 탐색할 수 있습니다. 이런 방법으로, 그림 5.1처럼 클래스 color의 인스턴스에서 정의된 메서드 hue를 확인할 수 있습니다. 반대로, class 버튼을 이용하면 메타클래스(이번 경우 Color 클래스)를 검색하고 편집할 수 있습니다.


클래스 메서드

클래스 메서드는 그럭저럭 쓸모가 있습니다. 좋은 예 몇가지를 들어보기 위해 Color class를 탐색하겠습니다. 클래스에 정의한 메서드에는 두 가지 종류가 있음을 발견하실 것입니다: Color class>>blue와 같이 클래스의 인스턴스를 만드는 메서드들과 Color class>>showColorCube 처럼, 유틸리티 함수를 수행하는 메서드가 있죠. 가끔 다른 방식으로 사용하는 클래스 메서드를 보겠지만, 이 두 가지 종류가 일반적입니다.

클래스 측면(the class side)에서 유틸리티 메서드들을 배치하는 작업은 편리하며, class side 에서 유틸리티 메서드를 배치하기 편리한 이유는, 추가 객체를 먼저 생성할 필요 없이 메서스들을 실행해볼 수 있기 때문입니다. 이런 메서드 대부분은 쉽게 실행할 수 있도록 설계시 주석이 들어가 있습니다.


Squeak comment.png메서드 Color class>>showColorCube를 검색하고, 주석 "Color showColorCube" 에서 따옴표 내부를 더블 클릭한 다음, CMD-d를 타이핑 합니다.


이 메서드가 실행되는 결과를 확인할 수 있습니다. [실행결과를 취소(undo)하기 위해 World ▷ restore display (r) 을 선택합니다.]

Java와 C++에 익숙하다면, 클래스 메서드가 정적 메서드(static method)와 비슷하게 보일겁니다. 하지만, 스몰토크에서는 그렇지 않습니다: 비록 Java의 메서드가 정적인 프로시져statically resolved procedures라고해도, 스몰토크 클래스 메서드는 동적으로 내보내는dynamically-dispatched 메서드입니다. 이런 특성은 스몰토크에서는 클래스의 메서드에 대해 동적으로 상속inheritance, 재지정overriding, super-send등이 가능하지만 Java의 정적메서드에서는 이런 동작을 처리하지 못함을 의미합니다.

클래스 인스턴스 변수

일반적인 인스턴스 변수처럼, 클래스 인스턴스 변수는 클래스의 모든 인스턴스같이 동일한 이름의 변수를 가지게 되고, 현 클래스의 하위클래스에 대한 인스턴스는 이러한 변수 이름들을 상속받습니다. 다만 개별의 인스턴스는 각자의 값을 따로 가지게 되죠. 이런 성질은 클래스 인스턴스 변수들에게도 그대로 적용됩니다: 각 클래스는 그 자체로 private 클래스 인스턴스 변수를 가지게 됩니다. 하위클래스는 인스턴스 변수들이 가진 클래스 인스턴스 변수들을 상속하겠지만, 클래스 인스턴스 변수들의 고유사본을 갖게 됩니다. 객체가 인스턴스 변수들을 공유하지 않듯이, 클래스와 서브클래스간에는 클래스 인스턴스 변수를 공유하지 않습니다.

count라고 불리는 클래스 인스턴스 변수는 클래스에 따른 인스턴스가 얼마나 많은지를 알아보는데 사용할 수 있습니다. 하지만 서브클래스는 자신만의 count변수를 가지기때문에 하위클래스의 인스턴스는 별도의 인스턴스 카운트값을 가지게 됩니다.

Example: 클래스 인스턴스 변수는 서브클래스와 공유되지 않습니다. Dog(개)와 Dog(개)로부터 count 클래스 인스턴스 변수를 상속받는 Hyena(하이에나) 클래스를 정의한다고 가정해보겠습니다.


클래스 5.2 Dog와 Hyena

Object subclass: #Dog
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'SBE-CIV'

Dog class
  instanceVariableNames: 'count'

Dog subclass: #Hyena
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'SBE-CIV'


이제 Dog클래스의 count(카운트)를 0으로 초기화 하는 Dog의 클래스 메서드를 정의하고, count는 새로운 인스턴스가 만들어질 때마다 하나씩 증가한다고 생각해보겠습니다:


메서드 5.3: 새로운 Dog의 count 유지하기

Dog class>>initialize
  super initialize.
  count := 0.

Dog class>>new
  count := count +1.
   super new

Dog class>>count
   count


지금, 새로운 Dog의 인스턴스가 만들어질때마다, Dog의 count가 증가되며, Hyena를 만들 때도 값은 증가하지만, Dog과 Hyena의 count는 따로 계산됩니다:

Dog initialize.  
Hyena initialize.  
Dog count ⇒ 0
Hyena count ⇒ 0
Dog new.  
Dog count ⇒ 1
Dog new.  
Dog count ⇒ 2
Hyena new.  
Hyena count ⇒ 1


클래스 인스턴스 변수는 인스턴스 변수가 인스턴스에 대해 private 으로 처리되는 것처럼, 클래스 인스턴스 변수 또한 클래스에 자체적으로만 동작한다는 것을 기억해주세요. 클래스와 그 클래스의 인스턴스들이 전혀 다른 객체로 처리하는 등의 특성때문에 다음과 같은 결론을 얻을 수 있습니다.


클래스는 그 자체의 인스턴스 변수에 대한 접근성을 가지고 있지 않습니다.


클래스의 인스턴스는 그 자체의 클래스가 갖는 클래스 인스턴스 변수에 대한 접근 권한이 없습니다.


이러한 이유 때문에, 인스턴스 초기화 메서드는 항상 instance side 에서 정의해야 합니다: class side 에서는 인스턴스 변수에 대한 접근을 할 수 없기 때문에, 해당되는 변수들을 초기화할 수 없습니다. class side 은 새롭게 만들어진 인스턴스에 접근자를 사용해서 초기화 메시지를 보내는 것 외에는 할 수 없습니다.

비슷한 이유로, 인스턴스는 이들 클래스에 대해 접근자 메시지를 사용해서 간접적으로 클래스 인스턴스 변수에 접근해야 합니다.


Java는 클래스 인스턴스 변수와 비슷한 것이 없습니다. Java와 C++의 정적 변수는 5.7장: 공유 변수 에서 알아볼 스몰토크의 클래스 변수에 더 가깝습니다.


Example: Singleton 을 정의합니다. Singleton 패턴[3]은 클래스 인스턴스 변수와 클래스 메서드의 표준적인 사용방법입니다. WebServer 클래스를 구현하고 이 클래스에 대한 단 하나만의 인스턴스만 존재할 수 있도록 Singletone 패턴을 적용한다고 가정하겠습니다. 우리는 시스템 브라우저에서 instance 버튼을 클릭한 뒤, 아래처럼(클래스 5.4) 클래스 WebServer를 정의할 수 있습니다.


클래스 5.4: singleton 클래스

Object subclass: #WebServer
  instanceVariableNames: 'sessions'
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Web'


그 다음, class 버튼을 클릭하여, 클래스 측면에서 인스턴수 변수 uniqueInstance를 추가합니다.


클래스 5.5 singleton 클래스의 class side

WebServer class
  instanceVariableNames: 'uniqueInstance'


결과를 보면 WebServer 클래스는 superclass 와 methodDict 와 같은 상속 변수들뿐만 아니라 다른 인스턴스 변수도 갖고 있다는 것을 알 수 있습니다.

메서드 5.6 처럼 uniqueInstance라는 인스턴스 변수와 같은 이름으로 클래스 메서드를 정의할 수 있습니다. 이 메서드는 uniquenInstance 인스턴스 변수를 초기화 했는지 확인합니다. 만약 인스턴스 변수를 초기화 하지 않았다면, 인스턴스 메서드를 만들고 클래스 인스턴스 변수 uniquenInstance에 만든 메서드를 할당할 것입니다. 마지막으로 uniquenInstance 값을 반환합니다. uniquenInstance는 클래스 인스턴스 변수이므로, 이 메서드는 그 클래스 인스턴스 변수에 직접 접근할 수 있습니다.


메서드 5.6: uniqueInstance (in class side)

WebServer class>>uniqueInstance
  uniqueInstance ifNil: [uniqueInstance := self new].
   uniqueInstance


WebServer uniquenInstance를 실행한 뒤 제일 처음으로. WebServer 클래스의 인스턴스가 uniqueInstance 변수를 만들고 할당합니다. 그 뒤 이전에 만든 인스턴스를 새로 만든 인스턴스 대신 반환하게 되죠.

참고로 메서드 5.6에 적혀있는 인스턴스 생성 코드는 WebServer new가 아닌 self new 처럼 작성했습니다. 이런 방법에는 어떤 차이가 있을까요? 당신은 uniquenInstance 메서드는 WebServer에 정의한 것이기 때문에, 이 둘은 동일한 수준에 있다 생각할 수 있습니다. 물론! WebServer클래스의 서브클래스를 만들어 내기 전까지는 같습니다. 하지만 ReliableWebServer 클래스가 WebServer의 서브클래스 이고 uniqueInstance를 상속한다고 가정한다면, ReliableWebServer uniqueInstance 는 WebServer가 아닌 ReliableWebServer 클래스에 반응합니다: 이 경우 self는 ReliableWebServer만을 대상으로 동작하게 하기 위한 확실한 방법이 됩니다. 왜냐하면 이런 (self를 사용하는 등의) 작업의 결과는 동작을 해당 클래스로 한정되게 해주기 때문이죠. 또한 WebServer와 RealiableWebServer가 uniqueInstance 라고하는 각각 별도의 클래스 인스턴스 변수를 가지게 된다는걸 기억하세요. 물론 이 두 개의 클래스의 인스턴스 변수는 제각기 다른 값을 가집니다.


Notes

  1. 글쎄요, 대부분, 스퀵에서, 문자열 pvt 로 시작하는 셀렉터인 메서드는 private입니다: pvt 메시지는 클래스(인스턴스) 내부에서만 사용할 수 있죠. 이런 특성이 있어도 pvt 메서드를 많이 사용하지는 않습니다.
  2. private 프로토콜로 메서드 또는 셀렉터를 만들었다고해서 그게 딱히 외부에서의 접근에 대해 보호받는건 아니라는 의미입니다. 분류는 해놓을 수 있지만 굳이 사용하려한다면 사용은 할 수 있다는 의미인거죠
  3. Sherman R. Alpert, Kyle Brown and BobbyWoolf, The Design Patterns Smalltalk Companion. Addison Wesley, 1998, ISBN 0–201–18462–1