SmalltalkObjectsandDesign:Chapter 02

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 2 장 클래스와 상속

클래스와 상속

앞장에서는 객체의 인지 및 프로그래밍과 관련된 잠재력을 암시했다. 이러한 잠재력을 충족시키기 위해서는 마음속으로 먼저 정리할 필요가 있다. 그러기에는 두 가지 구성 원칙, 즉 클래스와 상속이 필요하다.


클래스

객체는 어떻게 생성하는가? 일부 언어에서는 한 번에 빌드해야 하는 경우도 있지만 당신을 대신해 객체를 생성해주는 매커니즘이 있다면 더 편리할 것이다. 이러한 매커니즘을 클래스라고 부른다.

클래스는 프로그래밍 객체를 생성하는 팩토리(factory)으로 보면 된다. 각 팩토리는 한 가지 종류의 객체 또는 제품을 생산한다. 가령 은행 계좌를 생산하는 BankAccount 클래스, 사용자 인터페이스의 메뉴를 생성하는 Menu 클래스, 또는 실제 사전과 같이 행동하는 객체를 생성하는 Dictionary 클래스를 가질 수 있겠다.

스몰토크에서 Dictionary 클래스를 사용하는 방법은 아래와 같다:

Dictionary new

좌측부터 우측까지 Dictionarynew 라는 이름의 메시지를 수신한다. Dictionary 는 클래스 또는 팩토리이기 때문에 새로운 dictionary 객체를 생성함으로써 new 에 응답한다. 이제부터 이 객체에 어떤 일이 발생하는지는 전적으로 당신에게 달려 있다. Dictionary 이기 때문에 새로운 엔트리의 추가를 허용하는 add: 와 같은 연산을 가질 수 있다. 그리고 점차적으로 추가하여 원하는 유형의 사전으로 만들 수 있다.

현실적에서 이와 같은 dictionary 객체를 계속 사용하고 싶다면 우리가 그것으로 참조할 수 있는 핸들(handle)을 구축해야 한다. 즉 X 와 같이 변수명을 사용할 것이다:

X := Dictionary new


=는 스몰토크의 지정문(assignment)이다. 이는 Dictionary new 의 결과를 변수 X에 할당하고 싶음을 의미한다. Assignment는 '좌측부터 우측으로' 규칙에 예외가 된다. 먼저, 스몰토크는 지정문의 우측을 (Dictionary new ) 실행한 후 그 결과를 좌측 변수로 (X) 할당한다.

이제 사전을 X 로 참조하면서 사전에 새로운 내용을 추가할 수 있다:

X add: ...an entry...


그리고

X add: ...another entry...


다른 사전이 필요하다면 execute를 실행하면 된다:

Y := Dictionary new


XY 는 두 개의 구별된 dictionary 객체를 지칭하지만 동일한 클래스, Dictionary 로부터 비롯된다. 그리고 동일한 클래스 또는 팩토리로부터 비롯되어 둘은 유사하게 행동한다. 둘 다 사전에 적절한 연산을 지원하는데, 엔트리 추가, 검색(looking up), 또는 제거를 예로 들 수 있겠다. 이러한 연산을 이용해 훨씬 다른 방법으로 확대할 수 있다-X 는 덴마크어 사전에, 그리고 Y 는 포르투갈어 사전에 넣을 수 있겠다.

객체 지향 시스템에서 dictionary 객체들은 어떤 유형의 정보를 다른 유형의 정보로 관계시키는 데에 널리 사용된다. 사전을 사용하기도 전에 스몰토크에서는 많은 사전들이 표시된다 (스몰토크의 이미지에ㅡ페이지 9 참고); 그 중 대부분은 인지하지도 못할 것이다. 그 중 하나, system dictionary 는 스몰토크의 중심 객체이다. 이는 변수와 그들이 참조하는 객체들 간 관계(association)을 기록한다. 예를 들어, 스몰토크가 아래를 실행하고 나면:

X := Whale new


system dictionary 는 X 를 실제 whale 객체로 관계시키기 위한 엔트리를 포함한다. 라이브 스몰토크 이미지에는 수백 개의 다른 dictionary가 존재한다; 그들은 모든 유형의 관계를 유지한다ㅡ문자 이름(character name)부터 숫자값까지 ('XKunderscore' 부터 95까지), 마우스 이벤트부터 윈도우 시스템 이벤트 번호까지 ('WmButton1down'부터 113까지). 이러한 dictionary 들은 스몰토크를 실행시키는 데에 결정적이지만 대부분은 당신이 모르는 사이에 작동한다.

Chapter 02 01.png

클래스가 있는 스몰토크와 같은 언어에서는 객체들이 개별적이지 않다. 동일한 클래스로부터의 객체들은 동일한 연산을 갖는다-따라서 그들의 행위는 동일하다. 우리는 객체들을 상자로 둘러쌈으로써 객체들이 동일한 클래스에서 발생하였음을 나타낼 수 있다. 이러한 표현은 제 1장 (페이지 8) 끝에 표시된 객체의 그림보다 혼동이 덜하다; 소프트웨어가 더 조직화된 것이다. 상속을 논하면서 좀 더 조직된 모습을 제공할 것이다.


"클래스"라는 단어

"클래스"는 객체 지향 프로그래밍보다 먼저 발생했다는 의미를 가진 단어라 오해를 불러일으킨다. 학생들은 흔히 "class"를 "set"와 동의어로 사용하게끔 교육 받는다. 반면 수학자들은 약 100년 전에 set로 취급하기엔 너무 큰 컬렉션을 지칭할 때 "class"를 사용했다. (그러한 컬렉션의 예제가 "class of all sets(모든 set의 클래스)"이다.)

물론 학생과 수학자 둘 다 심중에는 엔티티의 컬렉션에 대한 관념을 어느 정도 갖고 있다. 하지만 객체 지향 개발자들에게 더 나은 "클래스"의 개념적 모델은 컬렉션보다는 팩토리이다. (실제로 "이 객체는 저 클래스에 의해 생성되었다"란 의미를 인식하고 있다면 "이 객체는 저 클래스에 속한다,"라는 식으로 간단하게 표현한다고 해서 죄는 아니다.) Onion 클래스를 모든 양파의 컬렉션으로 생각하는, 경험이 부족한 디자이너들은 양파를 잡화점의 선반에 있는 양파와 쇼핑 바구니에 있는 양파로 분할할 때 문제에 직면한다; 그들이 실제로 필요로 하는 것은 양파를 담는 상자(선반, 쇼핑 바구니 등)인데도 그저 더 많은 클래스의 생성을 (ShelfOnion, BasketOnion, …) 시도한다. 상자에 관한 내용은 제 6장을 참고한다.

"클래스"와 자주 혼동되는 또 다른 단어로 "타입"을 들 수 있다. 일반 객체 지향 프로그래머들에게 이 두 가지 용어는 의미가 동일하다. 사실 많은 논의에서는 이 단어들을 서로 바꿔 사용해도 별 해가 되지 않는다. 하지만 사실상 타입은 클래스가 아니며 이것을 클래스로 취급할 시 아주 위험한데, 그 이유는 추후에 (제 17장) 다루고자 한다. 그 전에 둘의 차이에 관해 법석을 떨면 잘난 체 밖에 안될 것이며, 종종 Onion 을 "클래스" 대신 "타입"이라고 불러 입게 되는 해는 최소이므로 그 정도는 무릅써도 되겠다. (추상적 데이터 타입 또는 데이터 타입이란 용어는 소프트웨어 공학 논의에서 발생하기도 한다. 이는 "타입"과 아예 동일하거나 동의어에 가깝지만, 그 자체나 미묘한 차이는 이 책에서 다루지 않을 것이다.)


상속

Chapter 02 02.png

두 번째 구성 원칙인 상속은, 당신의 프로그래밍 언어로 클래스가 다른 특별한 유형의 클래스임을 명시할 수 있음을 의미한다. 모두 학교에서 학습한 개념이다. 우측의 그림을 보면, 곤충은 동물의 특별한 종류이고 ("특수화"), 나비는 곤충의 특별한 종류임을 알 수 있다. 우리는 이와 같은 계층 구조적 그림에 대한 연구를 분류학이라고 불렀다. 객체 지향 프로그래머들은 그림에서 클래스의 계층구조를 설명하며 이 계층구조에서 ButterflyInsect서브클래스, 또는 그와 동등하게 InsectButterfly슈퍼클래스라고 말한다. 그들은 또 흔히 ButterflyInsect 로부터 상속 된다고 말한다. 인공지능에서 근무하는 사람들은 종종 이러한 관계를 AKO-A-Kin-Of(~의 일종)-라고 부른다. ButterflyAKO Insect(곤충의 일종)이다. 용어를 뭘로 하든 학교에서 학습한 내용, 즉 사물을 특수화의 증가 정도에 따라 분류하는 것을 기본 개념으로 한다.

린네(Linnaeus)로 더 유명한 식물학자 Carl von Linne은 18세기에 식물과 관련해 (동물도 포함해) 이러한 방식의 사고를 널리 퍼뜨렸다. 요즘 아동들은 이 개념을 너무 당연하게 생각한다; 식물과 동물의 분류학을 지루한 실습에 불과하다고 생각한다. 하지만 그 이후부터 개념은 매우 확장되어 사람들이 세계에 대해 생각할 수 있게 되었다. 예를 들어 만일 나비와 곤충을 연결할 수 있다면, 곤충에 대해 이미 갖고 있는 지식으로 내가 재사용하도록 도와주는 정신적 목발(mental crutch)을 구축하는 것이다. 곤충에 대해 사실이라고 알고 있는 모든 것이 자동적으로 나비에 적용된다ㅡ다리가 6개, 알을 부화, 변태(metamorphose), 신체에 난 구멍으로 숨을 쉬는 등. 내가 해야 하는 일은 나비가 곤충의 일종임을 규정하는 것이었다 (또는 "상속되었다"거나 "~의 서브클래스이다"라고 말하는 것). 작은 AKO 관계를 명시하는 수고로 많은 인식이 자유로워진다. 이제 이 개념을 스몰토크에 적용해보도록 하자. 스몰토크의 상속 계층구조의 일부는 다음과 같다:

Chapter 02 03.png

Float 클래스는 Number 의 특별한 유형이다. Integer 클래스도 마찬가지다. Number 객체에 true인 모든 것은 (덧셈, 곱셈 등이 가능) Float 또는 Integer 객체에도 true이다. 린네 생물학적 분류학의 개념과 마찬가지지만, 이번 것은 스몰토크에서 클래스의 분류학이라는 점이 다르겠다.

Object 클래스는 위의 그림에서 최상위 클래스로, Animal 클래스와 동등하다. 린네의 그림에서 모든 것이 Animal 의 유형이듯이 스몰토크에서 모든 것은 Object 의 일종이다. 이 그림은 "스몰토크에선 모든 것이 객체이다"라는 표현을 정당화한다. 그리고 Animal 이 구체적 특성이 거의 없이도 꽤 추상적이듯 Object도 그와 유사하게 추상적이다.

그림 속 다른 클래스들도 모두 직관적 의미를 지니는데, 한 가지 예외가 있다. Magnitude 클래스는 어떤 역할을 할까? 그 서브클래스 먼저 생각해보자. Magnitude 클래스에 true 라면 Date, Number, Time 객체에도 true여야 한다. 다시 말해, Magnitude 객체가 하는 일을 상상하기 위해서는 dates(일자), numbers(숫자), times(시간)이 공통적으로 가진 행위가 무엇인지를 찾아봐야 한다. 이러한 객체들의 내부는 생각하지 않도록 한다. 객체의 데이터가 저장되는 private한 내부보다는 그것의 행위-객체의 외부-를 생각하라. 그 행위, 외부가 객체의 사용자에게 중요하다는 사실을 항상 기억하라. 일자, 숫자, 시간에 공통적으로 있는 행위는 무엇인가?

여기 바람직하지 못한 추측이 있다: 곱셈과 덧셈. 두 숫자를 곱하는 것은 합리적이나, 1776년 7월 4일1066년 10월 14일과 같이 두 개의 일자를 곱하는 것은 비합리적이다. 이와 비슷하게 숫자의 덧셈은 괜찮지만 3시2시 또는 7월 4일10월 14일을 더하진 못한다.

그럼에도 불구하고 일자, 숫자, 시간에 관한 무언가는 유사하다. 무엇일까? 방금 결정한 바와 같이 산술은 아니다. 정렬 방식(sense of order)은 어떨까? 일자는 숫자나 시간과 마찬가지로 정렬된다. > (~보다 큰) 또는 <= (~보다 작거나 같은)와 같은 비교 연산은 어떤가? 어떤 일자는 다른 일자보다 클 수도 있고, 어떤 숫자가 다른 숫자보다 클 수도 있으며, 어떤 시간이 다른 시간보다 클 수도 있다. 이러한 비교는 서브클래스들이 공통적으로 가진 연산이다. 이는 세 가지 서브클래스에 복사하는 대신 단순히 Magnitude 클래스에 위치시킬 것이다. 그러면 서브클래스들이 그것을 상속한다. 이는 즉시 코드를 절약한다.

린네의 개념은 멋지긴 하지만 실제 객체 지향 시스템에서는 한 가지 조건이 있다. 서브클래스가 일부 연산에 대한 고유의 버전을 가져야 하는 경우가 있는데, 연산에 대한 코드는 객체 내부에서 데이터가 어떻게 표현되는지에 따라 의존하는 경향이 있고, 이러한 표현은 서브클래스마다 다를 수 있기 때문이다. 예를 들어, 부동소수점을 저장하는 데 사용되는 비트 규약(bit conventions)은 일자에 사용되는 것과 다르기 때문에 그것을 비교하는 코드도 틀림없이 다를 것이다. 이러한 상황은 후에 다루겠다. 지금으로선 개념적 수준에 머물러 세 가지 서브클래스로부터 Magnitude 슈퍼클래스로 비교의 개념을 팩토링하는 것만으로 축하하자.

이러한 논의는 분류학에 관해, 따라서 객체 지향 디자인에 관한 간단한 안내서를 설명한다. 객체의 클래스 간 공통성을 감지할 때마다 슈퍼클래스를 정의하고, 서브클래스로부터 공통성을 슈퍼클래스로 "팩토링"할 것을 고려한다. 그것이 바로 1970년대 후반에 스몰토크 디자이너들이 Date, Number, Time 를 다루던 방식으로, 그들의 결정은 지속성이 증명되어 오늘날 모든 상업적 스몰토크 시스템에 디자인이 발생한다.

ObjectMagnitude 아래에 작은 계층구조는 스몰토크의 클래스 계층구조의 일부에 불과하다. 전체 클래스 계층구조는 윈도잉, 컴파일러, 그래픽, 텍스트, 운영체제 서비스, 그보다 훨씬 더 많은 것을 위한 클래스들을 포함한다. 통틀어 fresh VisualSmalltalk (기존 Smalltalk/V) 이미지는 700개가량의 클래스, fresh VisualWorks (Smalltalk-80) 이미지는 대략 1400개 클래스, 그리고 fresh VisualAge(IBM Smalltalk) 이미지는 대략 2000개 클래스를 포함하고 있다. 브라우저로 알려진 도구로부터 도움말을 이용해 IBM Smalltalk 클래스 중 일부를 살펴보자:

Chapter 02 04.png


상단 좌측 윈도우패인(windowpane)은 Magnitude 클래스의 서브클래스에 초점을 둔다; 이를 스크롤하여 더 많은 클래스를 표시할 수 있다. Number 클래스가 강조되었으므로 중앙 윈도우패인에서 볼 수 있는 연산, 즉 *, +, - 등의 리스트는 숫자가 이해하는 연산들이다. 다시 말해, 이러한 연산들은 당신이 Number 객체로 전송할 수 있는 전보에 상응하는 것이다. 숫자는 일반 산술 연산을 이해함을 알 수 있을 것이다. 그리고 윈도우패인을 스크롤하여 훨씬 더 많은 연산을 볼 수 있다. 상속 덕분에 당신은 Number 의 슈퍼클래스에서 정의된 연산도 사용할 수 있다. Magnitude 를 강조함으로써:

Chapter 02 05.png


NumberMagnitude 로부터 상속되는 연산들을 볼 수 있는데, 이것이 바로 앞서 추측한 비교 연산들로, 몇 가지가 더 추가된다. Magnitude 의 서브클래스를 더 많이 표시하기 위해 창을 길게 만들었다. 위 그림에서 함축된 계층구조와 14 페이지에 실린 스몰토크 계층구조 그림을 비교해보라. (Integer 뒤에 붙은 세 개의 점은 Integer 의 서브클래스들이 있으나 현재 브라우저가 숨기고 있음을 의미한다. 이러한 클래스를 표시하고 숨기는 토글 기능은 Integer 위에 마우스를 올리고 더블 클릭을 통해 실행하면 된다.)

하단 윈도우패인을 살펴보는 것도 유용하겠다. 구문적 특징 또는 마지막 세 줄에 한눈을 팔면 안 된다; 첫 행이 가장 중요하다. 이는 Magnitude 클래스를 Object 클래스의 서브클래스로 만드는 스몰토크 코드이다. 이와 유사하게 앞의 스크린 샷에서는 NumberMagnitude 클래스의 서브클래스로 만드는 코드를 볼 수 있다.

클래스 브라우저는 그 형태가 제품마다 다르긴 하지만 모든 스몰토크 제품에서 표준 도구에 해당한다. 하나의 제품 내에서도 대안으로 사용할 수 있는 브라우저들이 있다. 위의 브라우저는 특히 불필요한 정보를 압축하는 데에 능숙하다; 그 외 정보를 더 많이 표시하는 브라우저도 있는데 때로는 원하는 정보 이상을 표시하기도 한다. 브라우저는 프로그래머가 스몰토크의 코드 라이브러리를 검색하게 해주는 가장 일반적인 방법이다. 추후 다루겠지만, 브라우저를 이용해 코드의 작성 또는 변경 후 컴파일도 가능하다. (속도는 덜하지만 유사한 도구들을 훌륭한 C++ 환경에서 이용할 수 있다.)

상속의 한 가지 사용으로 증분 프로그래밍이 있다. 자신의 요구를 거의 충족시킬만한 클래스를 발견했지만 사실상 충족시키지 못할 경우, 그로부터 서브클래스를 생성하여 클래스가 이미 제공하는 것과 자신의 요구를 구별시켜주는 상대적으로 소량의 코드를 작성하면 된다. 이러한 유형의 프로그래밍으로 황급히 빠지기 전에 알아두어야 할 점은, 무분별하게 연습하면 모호하고 독단적인 디자인이 생성될 수 있다는 점이다. 후에 여러 장에 걸쳐 양질의 상속 계층구조를 만들 때 직면하는 도전과제를 논하고자 한다. 지금은 상속을 재앙과 함께 희망의 가능성이 있는 판도라의 상자로 생각하라.

Chapter 02 06.png

앞에 실린 도식의 (12 페이지) 직사각형을 함유(nesting)하여 두 개의 클래스 간 상속 관계를 그려볼 수 있겠다. 스몰토크에서 가장 밖에 있는 직사각형은 Object 클래스를 나타낸다. 이 직사각형은 모든 객체를 포함하므로 "모든 것은 객체가 된다." 우측의 함유된 직사각형은 SavingsAccountCheckingAccountBankAccount 의 두 서브클래스이거나, IntegerFloatNumber 클래스의 두 서브클래스임을 나타낸다. 해당 도식의 표기법은 그저 표기법에 불과함을 명심하라. 객체를 말 그대로 그들의 클래스 내부에 있다고 생각하고 싶은 유혹을 뿌리쳐야 함을 기억하라. 객체는 그들의 클래스로부터 생성된다고 생각하는 것이 맞다-클래스는 새로운 객체를 생성하는 팩토리(공장)인 셈이다.

한 가지 기본 요점. 마음속에 서브클래스의 객체들이 그 슈퍼클래스의 객체들보다 더 많은 특징(quality)을 갖고 있는 직관을 마음에 심어야 한다. 나비는 곤충의 모든 특징을 비롯해 그보다 더 많은 특징을 갖고 있다. date 객체와 magnitude 객체도 마찬가지다. 서브클래싱(subclass)을 하면 당신의 객체들을 풍부하게 만드는 것이다. 미래에는 이에 이의를 제기하겠지만 현재로선 필수적인 직관이다.


용어

용어(terminology)를-다른 객체 프로그래머들에게 명확하게 정보를 전달할 필요가 있는 기술적 용어(technical jargon)-다룰 시간이 왔다. 아래 용어들은 스몰토크 커뮤니티에서 발생했지만 더 광범위한 C++ 커뮤니티를 비롯해 다른 객체 프로그래머들도 수용한다.

Chapter 02 07.png
  • 객체는 인스턴스라고 불리기도 한다.
  • 객체 내 데이터는 인스턴스 변수로 설명된다. 다시 말해, 인스턴스에 속하는 변수들이다.
  • 객체의 연산을 메서드라고 부른다.
  • 이미 알겠지만 메서드의 호출을 메시지라고 부른다.


메시지가 도착하는 객체에서 메서드가 활성화된다. 메시지는 전보(telegram)와 같고, 메서드는 전보에 대한 응답으로 객체가 실행하는 행동을 의미한다. 간단한 예를 몇 가지 들자면:

클래스 인스턴스 메시지 효과 또는 리턴값
Account MySavings MySavings withdraw: 230 인출(withdrawal) 처리
Integer 134 134-95 39 반환
String 'hello' 'hello' size 반환
Set MySet MySet add: 'hello' 하나의 객체, 즉 'hello' 문자열을 MySet에 넣는다.


위를 대신해 사용되는 용어도 목격할 것이다. 객체의 사용자가 볼 수 있는 모든 메서드의 "이름"으로 구성된 객체의 외부는 아래와 같은 이름으로 불리기도 한다:

  • 프로토콜
  • 행위
  • 인터페이스
  • 서비스
  • Public member 함수 (C++ 용어)


객체의 내부인 인스턴스 변수는 아래와 같이 '객체의 ~'로 불리기도 한다:

  • 속성
  • 특성
  • 메모리
  • 상태
  • Private member 데이터 (C++ 용어)


역사적으로 정확하게 말하자면 add: 'hello' 가 아니라 MySet add: 'hello' 가 메시지다. 다시 말해, 메시지는 그들의 수신자 객체(receiver object)를 포함한다. 그럼에도 불구하고 '메시지'라는 용어는 보통 수신자 객체의 유무와 상관없이 사용된다. 대화에서는 별로 중요하지 않기 때문이다.

기술적인(technical) 여담: "모든 것은 객체이므로" 스몰토크 메시지들은 자체가 최고의(first-class) 객체이다. 즉, 전보는 당연히 행위를 가진다. (이러한 특징을 226 페이지의 ghost 디자인 패턴에서 남용할 것이다.) IBM Smalltalk는 위의 차이를 명시적으로 관찰함으로써 더 나아간다: 수신자 객체가 없는 "메시지"는 Message 클래스의 인스턴스이며, 수신자 객체가 있는 "메시지"는 DirectedMessage 클래스의 인스턴스이다.

재래식 언어(conventional language)에서 메시지와 함수 (또는 프로시저) 호출 간 거슬리는 유사점을 눈치 챈 독자들이 많을 것이다. 둘 다 연산의 호출이다. 게다가 "메시지"라는 용어가 동시 이벤트의 이미지를 만들어내는지는 모르지만 호출과 마찬가지로 메시지들도 동시적(simultaneous)이지 않다; 오히려 호출과 메시지 모두 동기식(synchronous)이다: 따라서 메서드 또는 프로시저가 실행되는 동안은 아무 일도 발생하지 않는다; 전송자 또는 호출자가 막는다(block). 다시 말해 전보의 전송자가 응답을 기다린다는 말이다.

하지만 메시지와 함수 호출 간에는 중요한 차이가 있다. 메시지는 항상 구별된 "argument," 즉 호출에 응답할 책임이 있는 수신자 객체를 갖고 있다; 일반적인 호출은 모든 argument를 peer로 취급한다.

메서드는 주로 구현부로 들어가는 모든 코드를 포함하는 연산을 의미한다. 하지만 때로는 이 모든 코드를 참조하지 않고 메서드만 참조하고 싶을 때가 있다; 그 "이름"만 참조하고 싶을 때가 있다. 메서드의 이름을 스몰토크에서는 선택자(또는 메시지 선택자)라고 한다. 아래 그림에서 일부만 표시되어 있는 메서드는 아직 읽을 준비가 되지 않은 코드에서 큰 부분에 해당하는 body이지만 선택자는 단순히 add: 에 해당한다.

Chapter 02 08.png


더 심한 문제는 C++ 프로그래머들이 선택자 대신 signature라고 부르거나 메서드 대신 함수라고 부를 때 발생한다. 일반 대화에서 대부분 객체 프로그래머들은 이러한 차이를 별로 신경 쓰지 않는다. 선택자를 말하면서 메서드라고 부르거나, 메서드를 의미하면서 메시지라고 부른다. 부디 이러한 용어들 때문에 낙심하지 말길 바란다. 형상을 확실히 파악하고 유지하면 된다: 전보(메시지)는 객체(인스턴스)에 도착하고, 객체는 그것(선택자)을 인지하여 적절한 코드(메서드) body를 실행한다.

데이터 위주의 개발 방법론들 중 스몰토크 개발자들이 일반적으로 선호하지 않는 방법론에서는 객체 내 복잡한(complex) 객체를 제외하고 객체 내에 가장 원시적(primitive) 유형으로 된 것들만-정수나 문자(character)와 같은-속성(attribute)이란 단어를 사용한다. 따라서 어떤 사람들은 내 자전거의 색상(color)은 속성에 속하지만 자전거 뒷바퀴는 속성이라기엔 너무 복잡하다고 말하기도 한다. 하지만 스몰토크 철학자라면 극단적인 데 대한 공평함(evenhandedness)을 주장하므로, 색상과 바퀴를 동일하게 취급한다. 그들은 peer 인스턴스 변수들로, 그 중 하나가 다른 하나보다 좀 더 복잡할 뿐이다.

Chapter 02 09.png

마지막으로, 정말로 실질적인 차이가 있다: 변수와 그 값을 의미하는 객체는 주의 깊게 구별을 해야 한다. 스몰토크에서는 변수를 객체를 향한 포인터로 생각하는 편이 올바르다. 예를 들어, 이 그림에서 MySet 는 실제 세트 object(인스턴스)를 가리키는 변수이다. 인스턴스 변수를 포함한 모든 변수는 객체를 향한 포인터로 생각해야 한다. 따라서 bicycle 객체 내의 wheel 또는 color 인스턴스 변수는 클래스의 실제 인스턴스, Wheel 또는 Color 를 가리킨다.


연습: 계층구조

실습에 사용할 계층구조 네 가지가 있다. 각 계층구조에서 그것이 상속을 합리적으로 나타내는지 결정하라.

Chapter 02 10.png


해결책과 논의: 복합(aggregation) 계층구조

Hierachy 1 은 분명히 상속이 아니다. 엔진은 특별한 자동차 종류가 아니다. 그럼에도 불구하고 해당 유형의 계층구조는 중요하다. 이는 부품의 계층구조를 설명하고, 다양한 이름으로 통한다: part-of, aggregation, assembly, whole-part, composite, 또는 has-a (자동 "has-a(~가 있다)" 대시보드). 사실 복합 계층구조는 상속 계층구조보다 더 기본적이다. 아이들은 클래스의 특수화를 생각하기 오래 전부터 사물들이 다른 사물들로 구성ㅡ손에는 엄지손가락이 "있다(has-a)"-됨을 인식한다. 복합 계층구조는 상속이 유명해지기 오래 전부터 프로그래밍에서 필수적이었다. 일부 권위자들은(authorities) 그들의 정의용 객체 지향 원칙들 중 하나로 복합 계층구조를 포함하기도 한다 [Booch 1994; Collins 1995]. 작은 대상을 모아 큰 대상으로 만들지 못하면 당신의 소프트웨어에는 희망이 없다.

Hierachy 2 는 상속의 훌륭한 예제이다. HatchbackStation Wagon 의 특별한 종류로 생각하거나 Sedan 이 특별한 종류의 Automobile 이라고 생각하는 것은 합리적이다.

Hierachy 3 이 상속이라고 생각된다면 Charles 가 특별한 종류의 Queen Elizabeth 라고 말하는 것처럼 이상하다. 게다가 상속 트리는 항상 클래스의 개별적 인스턴스가 아니라 클래스들을 묘사한다. Charles를 클래스로 (객체를 생성하기 위한 팩토리) 해석할 수 있는 방법으로 무엇이 있을까? 어떤 클래스, 즉 Person 이나 Royalty 클래스의 개별적 인스턴스로 생각할 수 있겠다. 따라서 Hierachy 3 을 상속으로 해석하게 되면 문제가 생긴다.

그럼에도 불구하고 혹자는 "상속"을 Hierachy 3 에 적용 가능하다고 주장할 수도 있겠다. 결국 Charles 는 그의 피부색, 혈액형, 심지어 금전까지 Queen Elizabeth으로부터 물려받기 때문이다. 문제는 "상속"의 방언적(vernacular) 사용은 객체 지향 프로그래머가 사용하는 "상속"과 동일하지 않다는 데에 있다. 이러한 유형의 계층구조를 원한다면 우리는 혈통적(genealogical), 계통적(family-tree), 또는 유전적(genetic)이라고 부를지도 모른다. (방언적 의미는 SELF라 불리는 언어에 깔끔하게 맞다. SELF는 객체 지향 프로그래밍에 대한 대안적 접근법으로서, 인스턴스(Charles)가 클래스에 의존하는 것이 아니라 다른 인스턴스(Elizabeth)에 행위를 의존한다.)

Hierachy 4 도 상속 계층구조가 아니다. 다시 복합 계층구조의 전형적인 예제로, 중요하긴 하지만 상속과는 다르다. 복합 계층구조와 공통된 내용, 즉 노드가 계층구조에 한 번 이상 나타날 수 있음을 보인다. ChipsCards 가 두 번씩 나타나는데, 이러한 모습은 상속 계층구조에서 절대로 볼 수 없다. 복합 대 상속 문제를 비롯해 그들 간 눈에 띄는 연결 관계를 제 9장에서 다시 살펴볼 것이다.

복합 계층구조를 다룰 때는 미묘한 차이를 기다려라: 엔진 등으로 구성된 자동차 집합(aggregation)은 pot과 chip 간 관계와 질적으로 다르다. Chip와 pot의 결합 관계보다 엔진과 자동차가 더 밀접하게 결합되어 있다. 객체 지향 디자이너들은 종종 pot과 그것의 chips간 느슨한 관계를 집합 대신 컨테이너(container)라고 부른다. Pots는 chips을 포함하고(앞의 예제), baskets은 onions를 포함한다. container 클래스는 제 6장에서 전체적으로 논할 것이다. 그 외에도 공유하는 차이점이 하나 더 있다. 집합에서 sub-object는 공유가 가능할 수도, 불가능할 수도 있다. 내 팔(arm)은 순전히 나의 것이지만, 워드 프로세스 문서에는 종종 다른 문서 객체들이 공유하는 sub-object가 (그래픽스 또는 스프레드시트) 있다.1


예제: 집합 더하기 상속

클래스 계층구조 브라우저 스냅샷에서 혹시 스몰토크에서 서브클래스를 정의하면 아래와 같이 통제하기 힘든 형태를 갖게 된다는 사실을 기억할 것이다:

Number subclass: #Fraction
instanceVariableNames: 'numerator denominator'
...


Chapter 02 11.png

해당 코드는 분수와 숫자 간 상속 관계를 명시하고, 동시에 분수의 분자와 분모를 나타내기 위해 인스턴스 변수를 정의한다. 우측은 fraction 객체 3/4를 그림으로 나타낸 것인데, 여기서 필자는 분수가 가져야 하는 메서드 일부를 스케치로 꾸며봤다. 인스턴스 변수는 사실상 분수의 부분이다. 인스턴스 변수들은 integer 객체 3과 4를 각각 나타내야 하므로 아래 스케치에 객체를 추가하겠다.

Chapter 02 12.png

필자는 정수가 가져야 하는 일부 메서드로 integers 객체들을 다시 꾸며봤다. 마지막 그림은 집합과 상속 간 상호작용을 표시한다: 3과 4는 3/4의 (집합) 일부인 동시에 FractionIntegerNumber 의 (상속) 서브클래스이다. 객체 지향 소프트웨어에서 집합과 상속 없이는 멀리 가지 못한다.


상속을 위한 구문

7개의 객체 지향 언어에서 상속을 명시하는 구문을 소개하고자 한다. 첫 번째는 방금 본 스몰토크 구문이다:

상속 구문 언어
Insect subclass: #Butterfly... Smalltalk
Insect Class Butterfly (... Simula-67
클래스 BUTTERFLY 상속 INSECT... Eiffel
@implementation Butterfly : Insect {... Objective-C
클래스 Butterfly가 Insect {...를 확장한다. Java
Butterfly = object (Insect) ...를 입력한다. Object Pascal
Butterfly : public Insect {... C++


순전히 표현식의 명료성을 비교하면 Eiffel 또는 Java를 능가하기 힘들다. 하지만 테이블에서 핵심은 모든 객체 지향 언어에 상속을 표현할 수 있는 직접적인 방법이 있다는 점이다.


예제: 스몰토크에서 상속

스몰토크에 실행(execute)을 요청한다고 가정해보자:

Whale new talk


좌측부터 우측 방향으로 진행하면, Whale 은 클래스이고 new 는 클래스의 새로운 인스턴스, 즉 whale 객체를 생성하며, 이 인스턴스는 talk 메시지를 수신한다. 필자가 시범을 보이는 시스템에서 스몰토크는 다음과 같이 응답할 것이다: I am pretty quiet. 그렇다면 talk 메서드는 어느 클래스에서 실행되었을까?

당연히 Whale 클래스를 먼저 살펴볼 것이다. 그 곳에서 talk 를 찾을 수 없다면 찾을 때까지 클래스 계층구조를 위로 타고 올라갈 것이다. Whale 클래스와 그 슈퍼클래스인 Mammal 이 강조된 두 개의 브라우저가 있다:

Chapter 02 13.png

Chapter 02 14.png


두 개의 클래스에서 정의된 인스턴스 메서드를 보여주는 브라우저는 없다. 우리는 talk 메서드는 계층구조의 더 높은 곳에서 상속되어야 한다고 추측하기 때문에 Animal 클래스를 살펴본다:

Chapter 02 15.png


이번엔 talk 선택자 뿐만 아니라 그 코드를 하단 패인(pane)에서 볼 수 있다. 게다가 이 코드는 우리가 찾던 문자열을 포함한다- 'I am pretty quiet'. 실행된 메서드가 틀림없다.

이제 whale 인스턴스들이 whales에 적절한 방식으로 말하면서 다른 동물들이 말하는 방식에는 영향을 미치지 않길 원한다고 가정해보자. animal 클래스에서 talk 메서드는 수정하지 않는 편이 나을 것이다. 대신 또 다른 talk 메서드를 whale 클래스 내에 작성할 것이다. 이 메서드에서 필자는 'I am pretty quiet' 를 whales에 좀 더 적절한 문자열로 대체할 것이다:

Chapter 02 16.png


이제 다음을 다시 실행하면:

Whale new talk


스몰토크는 다음을 표시할 것이다: I spout and sing!. 의도한 객체들, 즉 whales의 행위만 변경하고 나머지 시스템은 동요하도록 하였다. 필자가 아래를 실행하면:

Animal new talk


스몰토크는 다음을 표시할 것이다: I am pretty quiet.

상속은 우리가 원할 때 (I am pretty quiet) 슈퍼클래스로부터 기본 행위를 재사용하도록 해주는 동시 우리가 원할 때 (I spout and sing!) 행위를 오버라이드하고 변경하도록 해준다.

요약: 선택자가 (talk) 동일한 두 개 또는 그 이상의 메서드가 반응할 수 있는 경우마다 스몰토크는 클래스 계층구조를 상향 이동하면서 발견하는 첫 번째 메서드를 실행한다. 다시 말해, 계층구조에서 동일한 이름으로 된 메서드들이 위에 위치한 것들을 오버라이드하거나 가려버린다. (작은 예외는 (54 페이지) 특수 변수 super를 논할 때 함께 논할 것이다.)


연습: 클래스 계층구조 빌드하기

❏ IBM Smalltalk 또는 VisualAge에서 당신이 작성하는 코드는 무조건 Application에 속한다. 애플리케이션은 일반적으로 여러 개의 클래스와 그들의 메서드를 포함한다.


애플리케이션을 생성하라:

  1. transcript로부터 Smalltalk tools 메뉴를 드롭다운하고 Manage Applications를 선택하라.
  2. Application Manager에서 Application > Create > Application을 차례로 선택하라. (사전작업에 대해 기본 값이 선택될 것이다.)
  3. 새 애플리케이션을 선택하고 Applications > Browse Application 을 선택하라.


Chapter 02 17.png

❏ 우측과 같이 Smalltalk/V 튜토리얼에서 Digitalk에 의해 알려진 클래스 계층구조를 빌드하라. AnimalObject 의 새 서브클래스로 만드는 것부터 시작하라. 새 클래스를 정의하기 위해서는 제시된 슈퍼클래스를 선택하고 Add Subclass 메뉴 옵션을 선택하라. (나타나는 대화상자에서 subclass를 선택하라.) 이 단계를 마치면 애플리케이션 브라우저는 아래와 비슷한 모양을 할 것이다:


❏ 마지막으로, Animal 클래스에서 인스턴스 변수 name 을 정의하여 모든 동물에게 이름을 가질 수 있는 기능을 제공하라. 이는 위의 브라우저에서 보이듯이 Animal 에 대한 텍스트를 편집하고 Save 메뉴 옵션을 선택함으로써 완료할 수 있다.

Chapter 02 18.png


논평: 객체 지향 프로그래밍이란 무엇인가?

1980년대 중반에는 무엇이 "객체 지향 프로그래밍"을 구성하는 지에 대해 일치하는 의견이 별로 없었다. 모두들 스몰토크와 C++가 객체 지향적이라는 사실에는 동의했지만 일부는 Ada와 Modula-2 역시 객체 지향적이라고 주장했고, 일부는 C와 Pascal에서 객체 지향 프로그래밍을 실행해왔다고 말하기도 했다. 그 시절에는 자신이 선호하는 프로그래밍 스타일이나 언어가 부족하다는 인식을 꺼리던 사람들에 의해 수많은 비생산적인 논쟁이 벌어졌다.

이러한 논쟁을 딛고 일어서기 위해 커뮤니티는 확고한 기반이 필요했다. 다행히 1987년에 Peter Wegner가 객체 지향 언어에 대한 정의를 제시하였다 [Wegner 1987]. 정의는 기준점과 같은 역할을 한다; 정의는 임의의 평가 기준이므로 그에 관한 어떤 것도 내재적으로 옳다거나 그르지 않다. 우리는 그 유용성에 따라-우리 주위의 세계를 이해하는 데에 어떻게 도움이 되는지에 따라-판단하는 것이다. Wegner의 정의는 유용하다고 간주되었다; 사람들은 그것을 실행 가능한 기준점으로 받아들였고, 그렇게 함으로써 자신이 선호하는 프로그래밍 언어에 관한 선입견을 옹호하는 대신 실질적인 소프트웨어 문제들을 논하는 단계로 돌아갔다.

Wegner의 정의에는 세 가지 요소가 있는데, 특히 우리가 토론한 세 가지 원칙이다. 프로그래밍 언어가 객체 지향적이 되기 위해서 그는 아래의 조건들을 충족해야 한다고 주장했다:

  • 객체 기반이어야 한다. 즉, 그 내부에 캡슐화된 프로그래밍 객체를 쉽게 만들 수 있어야 한다.
  • 클래스 기반이어야 한다. 모든 객체는 클래스에 속해야 (클래스로부터 생산되어야) 한다.
  • 상속을 지원한다. 클래스는 서브클래스-슈퍼클래스 계층구조에서 배열될 수 있다.


그는 그의 정의를 다음과 같은 방식으로 묘사했다:

Chapter 02 19.png

세 가지 원칙-객체, 클래스, 상속-들은 객체 지향 개발을 논하기 위한 시작점이다. 대부분 권위자들은 정의에 늦은(late) 바인딩 또는 동적 바인딩(dynamic binding)을 추가하곤 하는데, 이는 polymorphism(다형성)에 관한 제 14장에서 다룰 것이다. 객체 지향적 패러다임의 정의적 특징이 있어야 하는지 여부는 권위자에게 달려 있다. 앞서 논한 바와 같이 어떤 사람은 집합을 정의에 추가한다. [Meyer 1988]는 쓰레기 수집(garbage collection)과 다중 상속을 강력히 주장하는데, 이는 중요한 주제들로서 제 9장과 16장에서 각각 논할 것이다. 다시 말하지만 "올바른" 정의란 존재하지 않는다. 정의에 어떤 내용을 포함해야 하는지와 관련해 무조건 이의를 제기하기보다는 어떤 주장들인지 이해하고 그것이 소프트웨어 개발에 어떻게 영향을 미치는지 이해하는 것이 더 중요하다.

한편 모두가 객체 지향이라고 동의하지만 Wegner 요구조건을 충족하지 않는 언어들도 있다. 여기서 가장 중요한 것은 클래스와 상속을 위임으로 대신하는 SELF 연구 언어(research language)이다. 위임은 첫 번째 객체가 처리할 수 없는 행위가 무엇이든 그것을 다른 객체로 위임하도록 해준다. SELF는 [Chambers 1989]를 참고하고, 그 외 위임과 관련된 논의는 [Lieberman 1986; Lalonde 1986]을 참고하라.

Wegner의 세 가지 객체 지향 프로그래밍 원칙은 이미 모든 사람의 경험에 적용된다. 냉장고를 조절하거나 자동차를 운전할 때 당신은 캡슐화된 객체를 사용하는 것이다; 내부 작업을 인식할 필요는 없다. 개의 개념을 생각해보면 일반 개들 사이에서 공유하는 특징이 생각날 것이다ㅡdog 클래스. 연속된 특수화 수준을 생각할 때, 즉 가구 다음에 쇼파를 생각하는 경우 당신은 그것을 서브클래싱 또는 상속하고 있다. 이러한 의견들 중에 새로운 것은 하나도 없으며 프로그래밍의 영역으로 옮겨올 뿐이다.


논평: 다른 언어들

세 가지 원칙이 적용된 언어들을 정렬하면 다음과 같다:

객체 클래스 상속
Ada +
APL
C
CLOS + + +
CLU + +
COBOL
C++ + + +
Eiffel + + +
FORTRAN
Java + + +
Modula-2 +
Objective-C + + +
Pascal
Prolog
Simula-67 + + +
Smalltalk + + +


언어가 어떤 특성을 (객체, 클래스 또는 상속) 갖는지는 의견 문제일 수 있다; 테이블에서 일부 엔트리에 대해 이의를 합리적으로 이의를 제기할 수도 있을 것이다. 예를 들어, Ada와 Modula-2에서는 객체 뿐만 아니라 클래스를 정의하는 것이 별 수고가 아니다. 또한 다수의 표준 비객체 지향 언어들의 변형체(variants)도 객체 지향적이다. 여기에는 Borland의 Pascal 제품들과 Apple의 Object Pascal, Ada95 (기존에 Ada9X로 알려짐), COBOL과 FORTRAN 버전들도 포함된다.

비합리적인 이의도 있을 것이다: 작업을 통해 C에서 객체를 빌드하는 것이 가능하다는 이유로 C와 같은 언어는 객체 지향적이라고 주장하는 이도 있다. 심지어 모든 언어가 객체 지향적이라고 주장하는 사람도 있다! 결국 각각이 모두 계산적으로 완전하므로 그 중 하나가 실행할 수 있는 작업은 모두가 실행할 수 있다고 말하는 것이다. 예를 들어, 이론상으로 그들 중 하나를 이용해 C++ 컴파일러를 작성할 수 있다. 따라서 그들은 모두 객체, 클래스, 상속을 지원할 수 있다. 객체를 (또는 클래스 또는 상속을) 지원하는 것이 어떤 의미인지에 대한 이러한 일반적인 해석은 지극히 비생산적이다. 우리는 객체를 일반적인 우회(circuitous route)를 통해 사용할 수 있다는 사실을 알고 싶은 것이 아니라, 힘들이지 않고 객체를 사용할 수 있게 해주는 구성(constructs)이 언어에 있다는 사실을 알고 싶은 것이다.


논평: 역사

플라톤은 "형태"에 관한 이론을 상정하였는데, 침대의 이상적인 형태는 세계 모든 일반 침대에 대한 기반이 된다고 말했다 [Plato 375 B.C.]. 그의 형상(form)들은 스몰토크 객체를 미리 예고했다고 말할 수 있는데, 이러한 역사적인 빚은 Smalltalk-72에 관한 기사에서 직접 인정해왔다 [Shoch 1979]. 하지만 플라톤이 강조한 내용은 객체 지향 프로그래밍과 상반된다: 플라톤은 일반 침대가 이상적인 침대보다 덜 중요하다고 주장하기 때문이다.

린네의 식물 분류는 [Linnaeus 1753] 국제적 표준이 되었다. 상속을 체계적으로 대규모에 적용한 사람은 물론 린네가 처음이었지만 상속에 대한 지적 근원은 플라톤의 제자, 즉 "동물을 별개로 말한다면 그 중 다수에 대해서도 똑같이 말해도 될 것이다,"라고 말했던 아리스토텔레스로 거슬러 올라간다 [Aristotle 330 B.C.]. 따라서 두 개의 클래스가 공통된 특징을 갖고 있다면 아리스토텔르스는 그러한 특징들을 오늘날 우리가 슈퍼클래스라고 부르는 것으로 기인함으로써 논쟁을 피할 수 있다고 제시한다.

상속과 객체 지향 프로그래밍은 1960년대 중반부터 일어났다. 스몰토크 자체는 1970년대에 Xerox PARC에서 발달했다. 그 작업은 첫 상업적 스몰토크인 Smalltalk-80과 함께 막을 내린다. 전체적인 역사는 [Kay 1993]을 참고하라. Smalltalk-80은 후에 ParcPlace의 Objectworks\Smalltalk와 VisualWorks (비주얼 프로그램 도구도 포함하는)로 발달했지만 여전히 Smalltalk-80로 불린다. Digital Smalltalk/V 군은 1980년대 중반에 창시되었고 현재 VisualSmalltalk로 알려져 있으며, IBM Smalltalk는 (종종 VisualAge로 bundle되는) 1994년에 나타났다. 1995년, ParcPlace와 Digitalk가 ParcPlace-Digitalk라는 회사로 합병되어 현재 두 개의 제품군을 (VisualSmalltalk와 VisualWorks) 하나의 dialect로 결합 중이다. 그 외 덜 알려진 Smalltalks들도 상업적으로 이용 가능하다.

ANSI 위원회는 클래스와 메서드의 중심(core body)을 구성하게 될 스몰토크 표준을 정의하고 있다. 하지만 이러한 중심은 특정 dialect의 작은 부분에 그칠 것이기 때문에 이러한 표준이 전체 스몰토크 애플리케이션의 이식 가능성을 보장하지는 못할 것이다.

아래 가계도(family-tree)는 객체 지향 언어의 연대기 일부를 보여준다. (객체 지향 언어들은 굵은 글씨로 표기)

Chapter 02 20.png


두 가지 초기 랜드마크로 Sketchpad와 Simula-67이 보인다. MIT에서 Ivan Sutherland에 의해 개발된 직접 조작 그래픽 시스템인 Sketchpad에는 앞서 논한 원칙들이 분명히 나타나지만 프로그래밍 언어는 아니었다. 따라서 사람들은 Simula-67을 첫 번째 객체 지향 프로그래밍 언어로 간주한다. Smalltalk-72와 Smalltalk-80 사이에 Smalltalk-74, -76, -78이 있었다; 상속은 Smalltalk-76에서 처음으로 나타났다. CLOS(Common Lisp Object System)은 Common Lisp 표준의 일부이다.


Notes