SqueakByExample:5.3
모든 오브젝트는 클래스의 인스턴스다
모든 오브젝트는 클래스를 가지고 있으며, 스몰토크는 오브젝트에 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) 는 수신자와 다른 점(point) 사이의 거리를 계산합니다. 수신자의 인스턴스 변수인 x와 y 두개는 메서드 안에서 직접 접근이 가능합니다. 그러나 자신 외의 다른 점(point)의 인스턴스 변수들은 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에 보이는 것처럼 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 클래스를 탐색하겠습니다. 탐색을 하고 나면, 당신은 클래스에서 정의된 두 가지 종류의 메서드를 발견하게 됩니다: Color class>>blue와 같이 클래스의 인스턴스를 만드는 메서드들과 Color class>>showColorCubedhk 처럼, 유틸리티 함수를 수행하는 메서드가 있죠. 가끔 다른 방식으로 사용되는 클래스 메서드를 보겠지만, 이 두 가지 종류가 일반적입니다.
클래스 측면(the class side)에서 유틸리티 메서드들을 배치하는 작업은 편리하며, class 측면에서 유틸리티 메서드를 배치하기 편리한 이유는, 추가 오브젝트를 먼저 생성할 필요 없이 메서스들을 실행해볼 수 있기 때문입니다. 이런 메소드 대부분은 쉽게 실행할 수 있도록 설계시 주석이 들어가 있습니다.
메서드 Color class>>showColorCube를 검색하고, 주석 "Color showColorCube" 에서 따옴표 내부를 더블 클릭한 다음, CMD-d를 타이핑 합니다.
이 메서드가 실행되는 결과를 확인할 수 있습니다. [실행결과를 취소(undo)하기 위해 World ▷ restore display (r) 을 선택합니다.]
Java와 C++에 친숙한 분들은, 클래스 메서드가 정적 메서드(static method)와 비슷하게 보일겁니다. 하지만, 스몰토크에서는 그렇지 않습니다: 비록 Java의 메서드가 정적인 프로시져라고해도, 스몰토크 클래스 메서드는 동적으로 내보내는dynamically-dispatched 메서드입니다. 이런 특성은 스몰토크에서는 클래스의 메서드에 대한 상속inheritance, 재지정overriding, super-sends등이 가능하지만 Java의 정적메서드에서는 이런것들을 처리하지 못한다는것을 의미합니다.
클래스 인스턴스 변수
일반적인 인스턴스 변수들과 함께, 클래스의 모든 인스턴스들은 동일한 변수 이름들의 세트를 소유하며, 그리고 그 클래스의 서브클래스의 인스턴스는 변수 이름들을 상속하지만, 그럼에도 불구하고, 각각의 인스턴스는 자체적으로 개별적 값의 세트를 갖습니다. 이 사실은 정확히 클래스 인스턴스 변수들에게도 적용됩니다: 각 클래스는 그 자체의 개별적(private) 클래스 인스턴스 변수를 갖습니다. 서브클래스는 인스턴스 변수들이 가진 클래스 인스턴스 변수들을 상속할 것이지만, 그것의 개별적 복사본들도 갖게 됩니다. 오브젝트가 인스턴스 변수들을 공유하지 않는 것처럼, 클래스들과 그 클래스들의 서브클래스들도 클래스 인스턴스 변수들을 공유하지 않습니다.
여러분은 count라 지칭되는 클래스 인스턴스 변수를, 여러분에게 주어진 클래스를 만드는 작업에 얼마나 많은 인스턴스를 실행하였는지를 지속적으로 알기 위해 사용할 수 있습니다.
예시: 클래스 인스턴스 변수는 서브클래스와 공유되지 않습니다. 우리는 Hyena(하이에나)가 Dog(개)로부터 클래스 인스턴스 변수 count를 상속받는 위치에서, 클래스 Dog(개)와 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으로 초기화 하고, 새로운 인스턴스가 만들어 질 때 count가 증가하도록 Dog를 위한 클래스 메서드를 정의한다고 가정해 보겠습니다:
메서드 5.3: 새로운 개의 count 유지하기
Dog class»initialize
super initialize.
count := 0.
Dog class»new
count := count +1.
↑ super new
Dog class»count
↑ count
지금, 우리가 새로운 Dog를 만들 때, 그것의 count가 증가되며, 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)인 것과 똑같이, 클래스 인스턴스 변수 또한 클래스에 개별적이라는 사실에 주의해 주십시오. 클래스와 그 클래스의 인스턴스들이 다른 오브젝트들인 것처럼, 이 사실은 다음의 즉각적인 결과들을 갖게 됩니다.
이러한 이유 때문에, 인스턴스 초기화 메서드는 항상 인스턴스 측면에서 정의해야 합니다- 클래스 측면에서는 인스턴스 변수에 대한 접근을 갖지 않으므로, 그 변수들을 초기화할 수 없습니다. 클래스가 할 수 있는 작업은, 단지 새롭게 만들어진 인스턴스에 접근자를 사용하여 초기화 메시지를 보내는 것입니다.
유사하게, 인스턴스들은 접근자 메시지를 그것들의 클래스에 보냄으로서, 클래스 인스턴스 변수에 간접적으로만 접근할 수 있습니다.
Java는 클래스 인스턴스 변수의 등가물을 갖고 있지 않습니다. Java와 C++ 정적 변수는 우리가 섹션 5.7: -모든 서브클래스와 그것들의 모든 인스턴스는 동일한 정적 변수를 공유한다-에서 논의할 스몰토크의 클래스 변수와 좀더 비슷합니다.
Example: 싱글톤을 정의합니다. 싱글톤 패턴[3]은 클래스 인스턴스 변수와 클래스 메서드의 전형적인 사용의 예시를 제공합니다. 우리가 클래스 웹서버(class webserver)를 사용하기를 원하고, 그것이 하나의 인스턴스만을 가지도록 확정하기 위해 싱글톤 패턴을 사용한다고 상상해 봅시다. 브라우저에서 인스턴스 버튼을 클릭하여, 우리는 다음과 같이(클래스 5.4) 클래스 WebServer를 정의할 수 있습니다.
클래스 5.4: 싱글톤 클래스(the singleton class)
Object subclass: #WebServer
instanceVariableNames: 'sessions'
classVariableNames: ''
poolDictionaries: ''
category: 'Web'
그 다음,class 버튼을 클릭하여, 클래스 측면에서 인스턴수 변수 uniqueInstance를 더합니다.
클래스 5.5 싱글톤 클래스의 클래스 사이드
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가 만들어질 것이고 uniquenInstance 변수에 할당될 것입니다. 그 다음에는 이전에 만들어진 인스턴스가 새로운 인스턴스를 만드는 대신 리턴될 것입니다.
메서드 5.6에 있는 조건문 내부의 인스턴스 생성 코드가 그 자체로 새롭게 작성된 것이고, WebServer에 새롭게 작성된 것이 아님에 주의하십시오. 차이가 무엇일까요? uniquenInstance 메서드가 WebServer 클래스에서 작성된 것이기 때문에, 여러분은 아마도 둘 다 동일한 것이라고 생각할 것입니다. 그리고, 실제로, WebServer의 서브클래스를 만들 때까지, 둘 다 모두 정확히 같습니다. 그러나 ReliableWebServer가 WebServer의 서브클래스라고 가정하고, uniquenInstance 메서드를 상속해 보십시오. 우리는 ReliableWebServer가 ReliableWebServer에 답변하기 위한 고유한 인스턴스 임을 명백하게 볼 수 있을 것입니다: self 사용은, 이 결과 발생을 보증합니다.그 이유는 이 작업이 각각의 클래스에 묶이기 때문입니다. 또한 WebServer와 RealiableWebServer가 각각 uniqueInstance라 지칭되는 그것들 자신의 클래스를 갖게 될 것이라는 것에 유의 합니다. 이 두 개의 변수는 물론 다른 값을 갖습니다.
Notes
- ↑ 글쎄요, 대부분, 스퀵에서, 문자열 pvt 로 시작하는 셀렉터인 메서드는 private입니다: pvt 메시지는 클래스(인스턴스) 내부에서만 사용할 수 있죠. 이런 특성이 있어도 pvt 메서드가 많이 사용되지는 않습니다.
- ↑ private 프로토콜로 메서드 또는 셀렉터를 만들었다고해서 그게 딱히 외부에서의 접근에 대해 보호받는건 아니라는 의미입니다. 분류는 해놓을 수 있지만 굳이 사용하려한다면 사용은 할 수 있다는 의미인거죠
- ↑ Sherman R. Alpert, Kyle Brown and BobbyWoolf, The Design Patterns Smalltalk Companion. Addison Wesley, 1998, ISBN 0–201–18462–1