ComputerProgrammingwithGNUSmalltalk:5.8

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

self와 super

이제 GNU 스몰토크의 새로운 2개의 키워드를 배울 시간입니다. 이제 볼 두 키워드의 용도는 같습니다. 클래스를 정의하는 동안 메시지를 받는 객체를 언급하는 것입니다. 차이점은 참조한 객체 상에서 메서드를 호출하려 할 때 나타날 것입니다.


self

먼저 볼 키워드는 self 입니다. 클래스 선언에서 이 키워드를 쓸 때, 메시지를 받는 객체로 처리합니다. 예를 들어, 기본 Human 클래스를 아래와 같이 정의할 수 있습니다.

"human_self.st"
"The second version of the Human class written using self keywords."

Object subclass: Human [
  | name age |

  setName: aName [
    name := aName.
  ]

  getName [
    ^name
  ]

  setAge: anAge [
    age := anAge.
  ]

  getAge [
    ^age
  ]

  introduceYourself [
    Transcript show: 'Hello, my name is ', self getName, ' and I''m ', self getAge printString, ' years old.'; cr.
  ]

  > aHuman [
    ^self getAge > aHuman getAge
  ]

  < aHuman [
    ^self getAge < aHuman getAge
  ]

  = aHuman [
    ^self getAge = aHuman getAge
  ]
]

| me myBrother | 

me := Human new. 
me setName: 'Canol Gökel'. 
me setAge: 24. 

myBrother := Human new. 
myBrother setName: 'Gürol Gökel'. 
myBrother setAge: 27. 

me introduceYourself. 
myBrother introduceYourself. 

(me < myBrother) printNl.

Hello, my name is Canol Gökel and I'm 24 years old. I am an Engineer.
Hello, my name is İsmail Arslan and I'm 23 years old. I am an Engineer.
false


차이점은 객체의 속성을 직접 사용하는 대신 self 키워드를 사용하여 객체의 속성에 접근한다는 것입니다. 예를 들어, self getName은 “메시지를 받을 객체에게 getName 메시지를 보내라”라는 뜻입니다.


self가 메시지를 받으면, 그 객체의 클래스에서부터 그것의 모든 상위클래스까지 메시지와 일치하는 메서드를 찾을 때까지 검색합니다. 이것이 self과 super의 차이이니까, (super의 설명이 나올 때까지) 잘 기억해 두세요.


위의 예제처럼 속성에 직접 접근 가능하더라도 접근자를 사용하는 것을 권장합니다. 이것이 객체지향의 캡슐화 속성이며, 좋은 프로그래밍 실천법입니다. 왜냐하면, 실생활에서 객체의 내부가 바뀌면, 무슨 일이 일어날지 알 수가 없기 때문입니다. 예를 들어, Molecule 객체가 있다고 가정합시다. 원자를 추가하려 할 때, 결합 방식, 분자 모형등의 여러가지 molecule 속성이 변화할 것입니다. 단순하게 oxygen := oxygen +1 라고 해서는 안 됩니다. 예상할 수 없는 모든 세세한 것들을 다룰 수 있도록 addOxygenAtom 과 같은 방법을 사용해야 한다는 것입니다.


물론 어떤 때에는 직접 접근해야 할 때가 있겠지만(접근자들이 대표적인 사례), 가급적이면 추상화시키도록 노력하세요.


super

직직접 클래스를 작성하여 메서드를 재정의할 때, 상위클래스의 메서드 정의를 가져와야할 경우가 있습니다. 상위클래스의 메서드를 완전히 바꾸기보다는 추가적인 동작만을 넣고 싶을 때가 그런 경우에 해당하겠습니다. 이 예제는 손목시계 (Watch) 클래스와, 이 클래스로부터 또 다른 클래스 방수되는 손목시계(water resistant watches) 를 만드는 예제입니다.

"watches.st" 
"키워드 super를 사용하는 방법을 보여주기 위하여 손목시계 클래스를 정의하는 프로그램" 

Object subclass: Watch [ 
    | style chronometerCapability | 

    <comment: '보통 손목시계를 정의하는 클래스'> 

    setStyle: theStyle [ 
        "손목시계의 양식을 결정한다." 

        style := theStyle. 
    ] 

    getStyle [ 
        "손목시계의 양식을 가져온다." 

        ^style 
    ] 

    setChronometerCapability: theChronometerCapability [ 
        "고정밀 시간 측정 능력을 명시하는 메서드" 

        chronometerCapability := theChronometerCapability. 
    ] 

    getChronometerCapability [ 
        "시계가 고정밀 시간 측정 능력을 가지고 있는지 판별하는 메서드" 

        ^chronometerCapability. 
    ] 

    listYourFeatures [ 
        "손목시계의 기능을 출력하는 메서드" 

        Transcript show: 'Style: ', self getStyle; cr. 
        Transcript show: 'Chronometer capabilities: ', self getChronometerCapability printString; cr. 
    ] 
] 

Watch subclass: WaterResistantWatch [ 
    | resistanceDepth | 

    <comment: '방수 시계를 정의하는 클래스'> 

    setResistanceDepth: aDepth [ 
        "방수가 보장되는 수심(깊이)를 결정하는 메서드" 

        resistanceDepth := aDepth. 
    ] 

    getResistanceDepth [ 
        "방수가 보장되는 수심(깊이)를 가져오는 메서드" 

        ^resistanceDepth. 
    ] 

    listYourFeatures [ 
        "방수시계의 기능을 출력하는 메서드" 

        super listYourFeatures. 

        Transcript show: 'Resistance depth: ', self getResistanceDepth printString; cr. 
    ] 
] 

| watch1 watch2 | 

watch1 := Watch new. 
watch1 setStyle: 'Analog'. 
watch1 setChronometerCapability: true. 

watch2 := WaterResistantWatch new. 
watch2 setStyle: 'Digital'. 
watch2 setChronometerCapability: false. 
watch2 setResistanceDepth: 30. 

Transcript show: 'The features of the 1st watch:'; cr. 
Transcript show: '------------------------------'; cr. 
watch1 listYourFeatures. 

Transcript cr. 

Transcript show: 'The features of the 2nd watch:'; cr. 
Transcript show: '------------------------------'; cr. 
watch2 listYourFeatures.

The features of the 1st watch:



Style: Analog
Chronometer capabilities: true

The features of the 2nd watch:



Style: Digital
Chronometer capabilities: false
Resistance depth: 30


이 프로그램은 먼저 일반 시계를 표현하는 Watch 클래스를 정의합니다. 아날로그인지, 디지털인지, 초정밀 시간 측정이 가능한지 등의 여부를 알 수 있는 style 이라는 속성과 chronometerCapability 속성도 지정할 수 있습니다. 이 속성에 대한 접근자 메서드를 정의하고 나서, 표준 출력에 요소들을 출력하기 위한 다른 listYourFeatures 메서드에 대해서도 정의합니다. 지금까지 특별한 내용은 없습니다.


Watch 클래스를 정의한 후, 이 클래스로부터 방수시계를 표현하는 WaterResistantWatch 클래스를 만듭니다. 이 객체에 방수 한계 심도를 지정할 새 객체 resistanceDepth를 추가합니다. 이제부터가 묘미입니다. 우리는 상위 클래스로부터 listYourFeatures메서드를 오버라이딩하여 메서드 시작 부분에 다음과 같은 한 줄을 써두었습니다.

  super listYourFeatures.

이 구문은 listYourFeature 메서드를 자기 자신에게 보내는 것이지만, self 키워드가 아닌 super 키워드에 보냈기 때문에 메서드의 정의를 탐색할 때, 자신의 클래스가 아니라 자신의 상위클래스(우리의 예제에서는 Watch클래스)부터 탐색한다는 점이 다릅니다. 따라서 WaterResistantWatch 클래스의 listYourFeatures메서드 대신 Watch 클래스의 listYourFeatures 메서드를 호출합니다.


이렇게 하는 이유는 Watch 클래스가 이미 WaterResistantWatch클래스의 속성을 많이 가지고 있고, 손목시계의 기능들을 나열하는 기능을 가지고 있기 때문입니다. 새 클래스 메서드에 기존 listYourFeatures 내용을 복사해서 붙여넣을 수도 있고 방금처럼 상위클래스를 내부에서 정의하는 메서드를 호출할 수도 있습니다. 프로그래머는 후자를 사용합니다. 소스코드를 다시 작성하는 작업도 줄이고, 소스코드를 리팩토링 할 때, 하나의 소스코드만을 수정해도 되기 때문입니다. super키워드를 사용했기 때문에, Watch클래스에 새 속성이 추가된다고 해도 우리의 코드(WaterResistantWatch)는 영향받지 않습니다. super키워드를 쓰지 않는다면, Water클래스의 코드가 바뀔 때마다, 그 부분을 WaterResistantWatch에 복사해야 합니다.


여기에 self 키워드를 사용하는 것은 잘못된 방법입니다. 왜냐하면 재귀적으로 자기 자신을 계속 호출하여서 무한 루프를 유발하기 때문입니다. 무한재귀를 차치하더라도, 우리가 원하는 코드는 상위클래스에 있지 우리 클래스에 있지는 않기 때문에, 그 문장은 우리가 원하는 것을 해주지도 못합니다.


프로그램의 나머지 부분은 Watch 클래스와 WaterResistantWatch 클래스의 시계를 정의하고, 각각의 속성을 설정한 후, 나열합니다. 의도한 대로, 두 번째 시계는 상위 클래스로부터 속성을 상속받아 같이 표시하며, 추가한 특수 속성 resistanceDepth도 같이 출력합니다.


Notes