SqueakByExample:8.1

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

Object

어떠한 의도 또는 목적에 대해서도 Object 는 상속 계층의 뿌리root(또는 최하위 계층) 가 됩니다. 실제로 스몰토크에서, 상속도의 진짜 최하위 계층은 ProtoObject 이며, ProtoObject 는 객체로서 가장된 최소한의 엔티티를 정의하기 위해 사용되지만, 지금의 스몰토크는 이런 ProtoObject 에 대한 내용을 크게 신경쓰지 않아도 됩니다.

Obejct 는 kernel-Object 카테고리에서 발견할 수 있습니다. 놀랍게도, 여기에서는 400개의 메서드(확장을 포함한)를 발견할 수 있습니다. 달리 말한다면, 스몰토크에서 사용자가 정의하는 모든 클래스는 사용자가 이런 메서드들을 아는지와는 상관없이 400개의 메서드를 자동으로 제공한다는 의미가 됩니다. 몇몇메서드가 없을수도 있는데 새버전의 Squeak 에서는 몇몇 필요없는 메서드를 제거할 수도 있기 때문이라는 것에 주의해야 합니다.


Object 클래스에 적혀있는 주석:

Object 는 클래스 계층도에 속한 대부분의 다른 클래스들을 위한 루트 클래스the root class입니다. 예외가 있다면 ProtoObject(Object 의 superclass)와 그것의 서브클래스들subclases입니다. Object 클래스는 접근, 복사, 비교, 오류 처리, 메시지 전송, 그리고 반영과 같은 모든 일반적인 객체에 공통으로 적용되는 기본 동작을 제공합니다. 또한 모든 객체들이 응답해야할 유틸리티 메시지는 이 곳에서 정의됩니다. Object 는 인스턴스 변수를 가지고 있지 않으며 그것(인스턴스변수)를 추가해야할 필요도 없습니다. 이런 특성은 Object 의 여러 클래스들이 특별한 실행(예를 들면 smallIntegerUndefinedObject)을 가지는 Object 부터 상속되었기 때문이거나, 또는 VM 이 특정한 표준 클래스의 구조와 레이아웃에 관해 알고있거나 그것들을 기반으로 하기 때문입니다.


만약 Object 의 instance side 에서 메서드 카테고리 탐색을 시작하면, 여기서 제공되는 몇가지 중요한 동작을 발견하기 시작할 수 있을겁니다.


출력(Printing)

스몰토크에서 모든 객체들은 그 자체의 출력 폼을 반환할 수 있습니다. 사용자는 워크스페이스에서 모든 표현식을 선택할 수 있고 print it 메뉴를 선택할 수 있습니다: 이런 동작들은 프로그램식을 실행하고 반환된 객체에게 그 자체를 출력하도록 요청합니다. 사실 출력에 대한 동작은 반환된 객체에 printString 메시지을 전송합니다. 메서드 printString 은 템플릿 메서드이며 그 핵심은 메시지 printOn: 을 그 자신의 수신자(receiver)에 발송하는것에 있습니다. printOn: 은 hook 메서드 로서 특별하게 취급됩니다.

Object>>printOn: 은 사용자가 가장 빈번하게 오버라이딩 하게되는 메서드중 하나가 될겁니다. printOn 메서드는 인자로 Stream 을 받은후, 받은 인자를 이용해서 String 으로 객체의 이름을 표현하는것을 만들어냅니다. printOn 의 기본적인 구현은 "a" 또는 "an" 뒤에 클래스 이름을 사용하는것 밖에 없습니다. Object>>printString 은 이렇게 만들어진 문자열을 반환 합니다.[1]

예를 들어보면, 클래스 브라우저는 printOn: 메서드를 재정의 하지 않으며, printSting 메시지를 Object 에서 정의된 메서드들을 실행하는 인스턴스로 전송합니다.

Browser new printString  'a Browser'


TTCFont 클래스에서 PrintOn: 특수화에 대한 좋은 예를 확인할 수 있습니다. 아래의 코드에서 볼 수 있듯이 클래스의 인스턴스를 출력할때, 폰트의 모음 이름, 크기 그리고 종속 모음 이름이 클래스 이름의 뒤에 같이 출력됩니다.


메서드 8.1: printOn: 메서드의 재정의

TTCFont>>printOn: aStream
  aStream nextPutAll: 'TTCFont(';
  nextPutAll: self familyName; space;
  print: self pointSize; space;
  nextPutAll: self subfamilyName;
  nextPut: $)


TTCFont allInstances anyOne printString       'TTCFont(BitstreamVeraSans 6 Bold)'


메시지 PrintOn: 은 storeOn: 과 같지 않다는것에 주의해주세요. storeOn: 메시지는 수신자를 재생성하는데 사용할 수 있는 프로그램식을 그 자체의(storeOn의)인자에 stream 으로 대입합니다. 이렇게 만들어진 프로그램식은 readFrom 메시지를 사용해서 읽어들일때 처리됩니다. pringOn: 은 단지 수신자의 텍스트 버전을 반환할 뿐입니다. 물론 텍스트로 만들어진 프로그램식이 수신자를 표현하는 자체 처리 프로그램식이 될 수도 있습니다.


표현식과 자체평가표현식 이라는 단어에 대해서 함수 프로그래밍에서, 프로그램식은 실행이 될 때 값을 반환합니다. 스몰토크에서, 메시지(프로그램식)들은 객체(값value)를 반환합니다. 그 자신이 스스로 값으로 인식되는 좋은 속성들을 가지는 객체들이 있습니다. 예를 들면, 객체 true 의 값은 그 자신으로서 true 라는 객체가 됩니다. 이러한 객체들을 자체평가 객체self-evaluating objects 라 부릅니다. 워크스페이스에서 객체를 출력(print it)할 때 객체의 출력용 버전을 볼 수 있습니다. 여기에 위에서 설명한 자체평가 프로그램식의 예제가 있습니다.

true ⇒ true
3@4 ⇒ 3@4
$a ⇒ $a
#(1 2 3) ⇒ #(1 2 3)


배열과 같은 몇몇 객체는 자체평가 또는 포함하고 있는 객체에 의존하지 않는다는 사실을 주의해주세요. 예를 들면, Boolean 으로 만들어진 배열은 자체평가가 되지면 persons 의 배열은 그렇지 않다는거죠. 스퀵 3.9에서 이러한 동작방식은 가능한 많이 (isSelfEvaluating 메시지처럼) 자체평가 형태로 컬렉션을 출력하기 위해 도입되었으며, 이는 특별히 중괄호 배열에 있어 true 가 됩니다. 다음 예제는 동적 배열의 구성요소들일 경우에만, 동적 배열이 자체 처리됨을 보여드립니다. 다음 예제는 동적배열의 구성요소가 자체평가형인 경우 동적배열이 자체평가로 처리됨을 보여줍니다:

{10@10 . 100@100} ⇒ {10@10 . 100@100}
{Browser new . 100@100} ⇒ an Array(a Browser 100@100)


리터럴 배열은 오직 리터럴만 포함할 수 있다는걸 기억해주세요. 그렇기 때문에 다음의 배열은 두 개의 점(point)을 가지지 않으며 여섯개의 리터럴 요소만을 가지게 됩니다.

#(10@10 100@100) ⇒ #(10 #@ 10 100 #@ 100)


printOn: 메서드의 대부분은 자체평가를 구현하는 특수화를 진행하게 됩니다. Point>>printOn: 과 Interval>>printOn: 의 구현은 자체평가방식이 됩니다.


메서드 8.2: Point의 자체평가

Point>>printOn: aStream
  "The receiver prints on aStream in terms of infix notation."
  x printOn: aStream.
  aStream nextPut: $@.
  y printOn: aStream


메서드 8.3:인터벌(Interval)의 자체평가(Self-evaluation)

Interval>>printOn: aStream
  aStream nextPut: $(;
    print: start;
    nextPutAll: ' to: ';
    print: stop.
  step = 1 ifTrue: [aStream nextPutAll: ' by: '; print: step].
  aStream nextPut: $)


1 to: 10  (1 to: 10)  "intervals are self--evaluating"


정체성(Identity)과 동일성(equality)

스몰토크에서 = 메시지는 객체가 같은지(동일성)를 테스트하며(예: 두개의 객체가 같은값을 가지는지에 대해), == 메시지는 객체의 정체성(identity)을 테스트 합니다(예: 두개의 프로그램식이 같은 객체를 나타내는지에 대한 부분)


객체 동일성 비교에 대한 기본구현은 객체의 정체성에 대한 테스트입니다.

메서드 8.4: 객체의 동일성

Object>>= anObject
  "Answer whether the receiver and the argument represent the same object.
  If = is redefined in any subclass, consider also redefining the message hash."
   self == anObject


아래에 보이는것은 자주 오버라이드 하게될 메서드 입니다. 복소수의 경우를 생각해주세요:

(1 + 2 i) = (1 + 2 i) ⇒ true "same value"
(1 + 2 i) == (1 + 2 i) ⇒ false "but different objects"

이렇게 동작하는 이유는, Complex 가 = 를 다음과 같이 오버라이드(override) 하고 있기 때문입니다.


메서드 8.5: 복소수를 위한 동일성

Complex>>= anObject
  anObject isComplex
    ifTrue: [ (real = anObject real) & (imaginary = anObject imaginary)]
    ifFalse: [ anObject adaptToComplex: self andSend: #=]


Object>>∼= 의 기본구현은 Object>>= 을 부정하는 의미가 됩니다. 일반적으로 변경해야할 필요는 없습니다.

(1 + 2 i) = (1 + 4 i)  true


만약 = 을 오버라이드 한다면, hash 를 사용한 부분 또한 오버라이드 하지 않으면 안됩니다. 클래스의 인스턴스가 Dictionary의 키로서 사용되었을 경우에, "같다"고 보이는 인스턴스는, hash값도 같아야 합니다:

메서드 8.6: hash는 반드시 복소수등을 위해 재실행되어야 합니다.

Complex>>hash
  "Hash is reimplemented because = is implemented."
   real hash bitXor: imaginary hash.


비록 = 과 bash 를 함께 오버라이드 해야한다고 해도, == 를 재지정하면 안됩니다. (객체의 identity 에 대한 의미는 모든 클래스에 동일합니다) == 기호는 ProtoObject 의 프리미티브 메서드 이기 때문입니다.


스퀵이 다른 스몰토크 구현과는 다른 몇가지 익숙하지 않은 동작을 한다는것에 주의해주세요: 예를들면 symbol 과 string 은 동일하지 않습니다.(이건 기능이 아니라 버그가 아닌가 라는 생각을 하고있습니다)


#'lulu' = 'lulu'    true
'lulu' = #'lulu'    true


클래스 멤버십

몇몇의 메서드를 이용하면 사용자는 객체의 클래스를 조회할 수 있습니다.

class. class 라는 메시지를 사용하면 모든 객체에 대해 객체의 클래스가 무엇인지를 요청할 수 있습니다.

1 class    ⇒    SmallInteger


이와는 반대로, 객체가 특정 클래스의 인스턴스가 맞는지의 여부에 대한 확인을 요청할 수 있는 방법도 있습니다:

1 isMemberOf: SmallInteger ⇒ true "must be precisely this class"
1 isMemberOf: Integer ⇒ false
1 isMemberOf: Number ⇒ false
1 isMemberOf: Object ⇒ false


스몰토크는 스몰토크 스스로를 이용해서 만들어져 있기 때문에, 사용자는 상위클래스와 메시지(12장을 참고해주세요)의 정상적인 조합을 사용해서 스몰토크의 구조 전체를 탐색할 수 있습니다.


isKindOf: Object>>isKindOf:는 수신자의 클래스가 인자클래스와 동일한지, 또는 서브클래스인지등을 반환합니다.

1 isKindOf: SmallInteger ⇒ true
1 isKindOf: Integer ⇒ true
1 isKindOf: Number ⇒ true
1 isKindOf: Object ⇒ true
1 isKindOf: String ⇒ false
1/3 isKindOf: Number ⇒ true
1/3 isKindOf: Integer ⇒ false


1/3 은 분수인 동시에 숫자의 한 종류이며, Integer 클래스는 분수 클래스의 super클래스이지만, 1/3 이 정수가 되는것은 아닙니다.


respondsTo: Object>>respondsTo: 는 수신자의 클래스가 인자의 클래스와 동일한지, 또는 서브클래스인지에 대한 정보등을 반환합니다.

1 respondsTo: #, ⇒  false


일반적으로 객체에 클래스를 질의하거나, 객체가 어떤 메시지를 이해할 수 있는지에 대해서 객체에게 요청하는 것은 좋은 방법이 아닙니다. 객체의 클래스에 근거해서 판단하는 대신, 객체에 메시지만을 보낸후 수신한 객체가 어떤 행동을 할지는 객체의 결정(예를들자면 객체의 클래스에 기반한)에 맡겨야 합니다.


객체의 복사

객체를 복사하는 것은 몇 가지 미묘한 문제들을 발생시킵니다. 인스턴스 변수들은 참조에 의해 접근되기 때문에, 객체의 얕은 복사는 인스턴스 변수에 대한 그 객체의 참조를 원본 객체와 공유합니다:

a1 := { { 'harry' } }.  
a1 ⇒ #(#('harry'))
a2 := a1 shallowCopy.  
a2 ⇒ #(#('harry'))
(a1 at: 1) at: 1 put: 'sally'.  
a1 ⇒ #(#('sally'))
a2 ⇒ #(#('sally')) "the subarray is shared"


Object>>shallowCopy 는 객체의 얕은 복사를 만드는 프리미티브 메서드입니다. a2 가 a1 의 유일한 얕은 복사이기 때문에, 두 개의 배열은 공통으로 겹치는 배열에 대한 참조를 공유합니다.


Object>>shallowCopy 는 Object>>copy 에 대한 "공용 인터페이스" 이며 만약 인스턴스들이 고유한 경우 반드시 재지정(override) 해야만 합니다. 예를 들면 Boolean , Character, Smallinteger, Symbol 클래스, UndefinedObject 등이 이 경우에 해당됩니다.


Object>>copyTwoLevel은 간단한 얕은 복사로 잘 되지 않을때, 정확한 작업을 진행합니다:

a1 := { { 'harry' } } .  
a2 := a1 copyTwoLevel.  
(a1 at: 1) at: 1 put: 'sally'.  
a1 ⇒ #(#('sally'))
a2 ⇒ #(#('harry')) "fully independent state"


Object>>deepCopy 는 임의로 객체의 깊은 복사를 만듭니다.

a1 := { { { 'harry' } } } .  
a2 := a1 deepCopy.  
(a1 at: 1) at: 1 put: 'sally'.  
a1 ⇒ #(#('sally'))
a2 ⇒ #(#(#('harry')))


깊은 복사의 문제는 상호 재귀적 구조에 사용되는경우, 종료되지 않는다는 것입니다:

a1 := { 'harry' }.  
a2 := { a1 }.  
a1 at: 1 put: a2.  
a1 deepCopy ⇒ ...does not terminate!


올바른 작동을 위해 deepCopy 의 재지정(override)을 할 수도 있지만, 보통 Object>>copy 가 더 나은 방법이라고 할 수 있습니다:


메서드 8.7: 템플릿 메서드로 객체를 복사하기

Object>>copy
  "Answer another instance just like the receiver. Subclasses typically override
    postCopy;
  they typically do not override shallowCopy."
  self shallowCopy postCopy


공유되지 않아야 할 모든 인스턴스 변수들을 복사하기 위해 여러분은 반드시 postCopy를 재지정해야 합니다. posetCopy는 반드시 super postCopy를 실행해야 합니다.


디버깅

이 부분에서 가장 중요한 메서드는 halt 입니다. 메서드에서 중단점(breakpoint)을 설정하기 위해, 메서드의 내용중 원하는 부분에 메세지를 보내는 "self halt" 를 삽입합니다. 메시지가 보내질 때, 실행은 중단되며, 디버거는 프로그램내의 중단된 지점에서 열리게 됩니다. (디버거에 관한 좀 더 자세한 내용은 6장을 보십시오.)

다음으로 중요한 메시지는 assert:이며, 이 메시지는 블록을 인자로 요구합니다. 만약 블록이 true 를 반환하면 계속 실행됩니다. true 가 아닌경우, AssertionFailure 예외가 발생됩니다. 만약 이 예외를 인지하지 못하면, 예외가 발생한 지점에서 디버거가 열립니다. assert: 메시지는 명세에 의한 설계를 진행하는 작업을 지원해야하는 작업등에 유용합니다. 객체의 공용 메서드에 대한 중요한 사전조건을 점검하는 작업등에서 사용되는경우가 일반적입니다. Stack>>pop는 다음처럼 쉽게 구현되어 있습니다:


메서드 8.8: 전제조건 점검

Stack>>pop
  "Return the first element and remove it from the stack."
  self assert: [ self isEmpty not ].
  self linkedList removeFirst element


TestCase>>assert: 와 Object>>assert: 을 혼돈하지 마십시오, Object>>assert 은 SUnit 테스트 프레임워크에서 발견할 수 있습니다(7장을 보십시오). 전자가 인자[2]로서 블록을 필요로하는 반면에, 후자는 Boolean 을 인자로서 받습니다. 하지만, 두가지 모두 디버깅에 유용합니다만, 사용목적은 크게 다릅니다.


오류 처리

이 프로토콜은 런타임 오류를 나타내는데에 유용한 여러 개의 메서드들을 포함하고 있습니다.

설정 브라우저(Preference browser) 에서 deprecation 을 활성한 경우에 self deprecated: anExplanationString 신호를 보내면 현재 메서드는 더이상 사용되지 않는것이 되며, String 으로 반환되는 인자는 대안을 제시하게 됩니다.

1 doIfNotNil: [ :arg | arg printString, ' is not nil' ]
       SmallInteger(Object)>>doIfNotNil: has been deprecated. use ifNotNilDo:


doesNotUnderstand: 는 메시지 lookup 에 실패할 때마다 전송됩니다. 기본구현은 그렇습니다만, 예를 들자면 Object>>doesNotUnderstand: 는 이 시점에서 디버거를 열게됩니다. 이 외에 다른 동작을 제공하려면 doestNotUnderstand: 재지정 하는것이 좋습니다.

Object>>error 와 Object>>error: 는 예외를 발생시키기 위해 사용될 수 있는 범용적인 메서드입니다. (하지만 대부분의 경우 kernel 클래스와 코드에서 발생하는 오류를 구분할 수 있도록 사용자가 별도로 예외를 발생시키는것이 좋습니다.)

스몰토크에서 추상 메서드는 규칙에 따라 self subclassResponsibility 의 본문에 구현되어 있습니다. 따라서 추상클래스가 우연히(실수로) 인스턴스화 되어 호출되어도 Object>>subclassResponsibility 가 실행됩니다.


메서드 8.9: 추상 메서드임을 신호로 알리기.

Object>>subclassResponsibility
  "This message sets up a framework for the behavior of the class' subclasses.
  Announce that the subclass should have implemented this message."
  self error: 'My subclass should have overridden ', thisContext sender selector
    printString


Magnitude, Number, Boolean 은 이 장에서 잠깐 살펴볼 추상 클래스들의 고전적인 예입니다.

Number new + 1        ''Error: My subclass should have overridden #+''


관례에 따라 서브클래스가 상속해서는 안되는 메서드를 알려주려먼 서브클래스쪽에서 self ShouldNotImplement 를 보냅니다. 이건 일반적인 경우 클래스계층의 설계가 잘못된 경우의 신호(signal)입니다. 그렇지만 단일상속에서 오는 한계때문에, 가끔 이런 예비수단을 사용해야만 하죠.

대표적인 예로서, 딕셔너리에 의해 상속되었지만 실행된 것으로 알려지지 않은 Collection>>remove: 가 있습니다. (딕셔너리는 removeKey:instead를 제공합니다.)


Testing

testing 메서드는 SUnit 테스트와는 아무 관계가 없습니다. testing 메서드는 여러분에게 수신자receiver의 상태에 대한 질문을 하고 Boolean(Boolean)을 반환하는 메서드입니다.

Object 는 여러개의 testing 메서드를 제공합니다. isComplex 는 이미 확인되었습니다. 그 외에도 isArray, isBoolean, isBlock, isCollection 등이 있습니다. 일반적으로 이러한 메서드들은 가능하면 사용하지 말아야 합니다. 그 이유는 메서드의 클래스를 위해 객체를 조회quering하는 작업은 캡슐화encapsulation에 위배a form of violation되기 때문입니다. 사용자는 객체의 클래스를 테스트 하는 대신에, 요청request을 보내서 객체로 하여금, 그 클래스를 어떻게 제어할지 결정하도록 해야합니다.

그렇지만, 이 테스트 메서드중 일부는 정말로 유용합니다. 가장 유용한 것은 아마도 ProtoObject>>isNil 과 Object>>notNil 일 것입니다.(Null 객체[3] 의 디자인 패턴을 사용하면 이런 메서드가 필요한 경우를 없앨 수 있습니다.)


initialize release

Object 가 아닌 ProtoObject 에 잇는 마지막 중요한 메서드는 initialize 입니다.


메서드 8.10: 빈 hook 메서드로서의 initialize

ProtoObject>>initialize
  "Subclasses should redefine this method to perform initializations on instance
  creation"


squeak 3.9 버전에서 이게 중요한 이유는, 기본적으로 시스템의 모든 클래스에서 new 메서드는 새로 생성된 인스턴스에 initialize 를 보내게 되기 때문입니다.

메서드 8.11: 클래스-사이드 템플릿 메서드로서의 new

Behavior>>new
  "Answer a new initialized instance of the receiver (which is a class) with no
    indexable
  variables. Fail if the class is indexable."
   self basicNew initialize


위의 내용은 initialize hook 메서드를 단순히 재지정하면, 클래스의 인스턴스가 자동으로 초기화된다는 의미입니다.


일반적으로 initialize 메서드는 상속된 모든 인스턴스 변수들을 위한 클래스 불변성을 확보하기 위해 super initialize를 수행합니다.(이런 동작이 다른 스몰토크에서 표준은 아니라는것을 주의해주세요)


Notes

  1. http://ta.onionmixer.net/wordpress/?p=215 페이지를 참고해주세요
  2. 실제로, 이것은 Boolean을 포함한 값을 이해하는 모든 인자를 취할 것입니다.
  3. BobbyWoolf, Null Object. In Robert Martin, Dirk Riehle and Frank Buschmann, editors, Pattern Languages of Program Design 3. Addison Wesley, 1998