DesignPatternSmalltalkCompanion:1.3
1.3 C++!=Smalltalk (또는 Smalltalk ~=C++)
스몰토크와 C++는 단순히 서로 다른 프로그래밍 언어가 아니다; 언어에 대한 설계와 언어로 프로그래밍하는 데에는 기본적인 차이가 있다. 둘 중 하나로 작업하는데 익숙한 설계자들은 디자인 문제와 해법을 서로 다르게 바라볼 것이다.
개발자가 작업하는 언어가 문제 해법에 대해 생각하는 방식에 영향을 미칠 것이라는 주장을 자세히 살펴보자. 자연언어를 다루는 심리 언어학 영역을 가장 높은 수준에서 살펴보면, 언어와 사고는 서로 밀접하게 연결되어 있으며 서로 영향을 미친다는 사실을 재빠르게 확인할 수 있다. Benjamin Whorf (1956)는 언어란 개인이 세상을 바라보는 방식 또는 개인이 사고하는 방식에 확실히 영향을 미친다고 가정했다. 이러한 워프의 가설(Whorfian hypothesis)이 프로그래밍 언어에도 사실이라고 주장하는 사람들도 있음은 쉽게 짐작할 수 있다: 서로 다른 언어에 대한 다른 구문과 제어구조는 그 언어로 문제를 해결하는 방식에 영향을 미치는 것이다 (예: Curtis, 1985, 특히 6장 참조).
이와 반대로 사고와 세계관은 언어에 영향을 미치는 것으로 보인다. 사실 많은 심리학 연구에서는 이것이 사실이라는 증거를 제시해왔다 (예: Anderson, 1985 참조). 따라서 Goldberg와 Kay (1977)가 주장한 바와 같이 객체지향 언어들은, 설계자들이 실세계에 대한 그들의 인식을 설명하는 모델에 더 가깝게 구축하도록 하기 때문에 “진화되었다”는 말이 사실일지도 모르겠다. 여러 저자들이 제시하였듯이 객체지향 디자인은 사람들이 문제 영역을 자연스럽게 모형화하는데 더 나은 짝을 제공하므로 절차지향식 언어의 설계보다는 좀 더 “자연적이다” (Rosson & Alpert, 1990; Cox, 1984).
앞의 두 가지 관점을 모두 뒷받침하는 Soloway, Bonar, Ehrlich (1983)는 사람들이 프로그래밍 해법의 설계와 관련해 자연히 생각하는 방식과 더 일치하는 인식을 제공하는 프로그래밍 언어가 사용하기 수월하다고 설명하였다. 이와 반대로 그들은 프로그래밍 언어가 개인이 선호하는 디자인 전략을 변화시키고 특정 언어의 구성에 경험이 많을수록 디자인 선호도가 바뀐다는 사실도 발견했다. 결론은, 누군가가ㅡC++, 스몰토크로ㅡ구현하는 언어가 시스템과 애플리케이션에 대한 사고 및 설계 방식에 영향을 미칠다는 것이다. 스몰토크 전문가들과 C++ 개발자들은 서로 다른 언어로 프로그램을 설정하는데 그치는 것이 아니다; 그들은 서로 다른 언어로 말한다. 예를 들어 스몰토크 프로그래머에게 클래스 객체는 클래스를 나타내는 진실된 스몰토크 객체인 반면 C++ 개발자들에게 클래스 객체란 사용자가 정의한 클래스의 인스턴스이다 (즉 내장된 C-언어 데이터 타입이 아니란 말이다). 두 언어와 환경 간에 개념적으로 중복되는 부분이 상당히 많음에도 불구하고 두 언어는 서로 상당히 다른 의견을 포함하고, 다른 문제를 표면화시키며, 최종적으로 프로그램 설계에 대한 서로 다른 사고로 이끈다.
반대로 설계 시에 목표어를 고려할 필요가 있다: "설계 시 언어를 고려하지 않을 경우 문제를 미해결 상태로 남길 수 있다…그러한 설계로 인해 형편 없는 프로그램이 되기도 한다"" (Smith, 1996a). 목표어를 선택 시 발생할 수 있는 제약과 기회를 고려하지 않고 설계하는 것은 실수일 것이다. 스몰토크의 경우 언어 자체뿐 아니라 클래스 라이브러리와 내장된 프레임워크도 고려해야 한다. 예를 들어, 비주얼웍스(VisualWorks)의 Mode-View-Controller(모델-뷰-컨트롤러) 프레임워크, 비주얼 스몰토크(Visual Smalltalk)의 Mode-Pane 프레임워크, IBM 스몰토크의 Motif-style 상호작용 프레임워크를 생각해보자ㅡ각 프레임워크는 실제로 대화형 애플리케이션의 설계를 시작하기도 전에 특정 디자인의 결정을 필요로 한다.
이러한 생각을 명심하고 스몰토크와 C++가 어떻게 다른지ㅡ구체적으로 말해, 두 언어가 어떻게 설계에 영향을 미치는지ㅡ간단히 살펴보도록 하자. 이를 살펴보는 목적은, 두 언어에는 수많은 기본적 차이가 있으므로 개발자들이 문제에 대해 생각하고 해법을 설계하며 디자인 패턴을 구현하는 방식이 서로 다를 수 밖에 없다는 우리 주장을 뒷받침하기 위함이다 (어느 한 언어가 뛰어나다는 주장을 하려는 것이 아니다; 두 언어의 비교를 통해 그 중 한 가지 언어를 선호하는 팬들을 언짢게 만들었다면 미리 사과하는 바이다). 이 과정에서 구체적 특성의 유무에 따라 영향을 받을 일부 패턴을 언급하고자 한다.
순수한 객체지향 대 혼합 객체지향
스몰토크는 "순수한"" 객체지향 언어이다. 스몰토크에서 모든 계산은ㅡ가장 원시적 수준(과 일반 프로그래밍 활동과 관련이 없는 수준)을 제외한ㅡ객체로 전송하는 메시지의 결과로만 발생한다. 모든 것은 객체이며, 여기에는 숫자, 문자, 문자열과 같은 원시 데이터 타입을 포함한다. 반면 C++는 복합 언어로서, 절차지향의 C언어 기반에 객체지향 특성들이 추가된 언어이다. 언어의 비객체지향적 특성을 이용할 수 있으므로 설계자들과 프로그래머들에게서 서로 다른 사고방식을 조성한다. 예를 들어, C++에서는 어떠한 클래스에도 부착되지 않은 전역함수를 가질 수 있는 반면 스몰토크에서는 각 기능성 부분의 책임을 누가 지는지를 반영해야만 한다. 복합 언어 접근법은 개인이 객체지향과 절차지향 패러다임의 이점을 모두 이용하는 프로그램을 사용하도록 하고, 레거시 C 코드로의 인터페이스가 훨씬 쉽다. 반면 복합 언어가 아닌 순수한 언어는 이해가 쉽다 (Rosson & Alpert, 1990).
객체로서의 클래스
모든 것을 객체로 간주하는 스몰토크에서 클래스란 first-class 런타임 객체를 의미한다. 따라서 메시지 송수신이 가능하고 일반적으로 어떤 계산이든 참여할 수 있음을 의미한다. C++에서는 이것이 불가능하다. 스몰토크에서 인스턴스 생성은 클래스 객체가 수행하는 업무 중 하나이지만, C++에는 언어 자체에 내장되어 있다. 따라서 스몰토크에서는 인스턴스의 생성과 행위 간 차이가 덜 명확하다: 가장 기본 형태에서 인스턴스 생성은 행위의 특수화에 불과하지만, C++에서는 엄밀히 구분된 범주이다. 필요한 자격을 갖춘 객체 특성들을 가진 클래스가 수 많은 패턴의 (Abstract Factory, Singleton, Factory Method 패턴 등) 스몰토크 버전에서 사용되는 경우를 살펴볼 것이다.
성숙하고 포괄적인 클래스 라이브러리
주요 스몰토크 환경의 이점 중 하나는 수 년 간 다듬고 디버깅해온 기반 클래스의 거대한 집합을 들 수 있다. 라이브러리의 광범위한 사용 결과, 이에 포함된 저수준의 추상 데이터 타입조차 시간이 지나면서 향상되어 광범위한 성능을 가진다. 이렇게 광범위한 기능성은 특정 설계 고려사항은 물론이고 심지어 일부 디자인 패턴 구현에 대한 고려사항조차 제거한다. 예를 들어, 스몰토크에서는 기본 Collection 클래스가 자체 반복(iteration) 메서드를 제공하기 때문에 내부 Iterator를 설계하거나 구현할 필요가 없다. Composite를 포함한 다른 패턴들도 광범위한 기반 클래스 라이브러리의 기능성을 재사용하여 혜택을 받을 수 있다. '모든 것은 객체다'란 주제로 다시 돌아가, 숫자, 문자열, Collection과 같은 추상 데이터 타입은 언어 자체에 내장된 블랙박스 데이터 타입이 아니라 기반 클래스 라이브러리에서 사용자가 수정이 가능한 클래스로서 구현된다. 즉 이러한 클래스 내에서 우리만의 메서드를 정의함으로써 객체의 기능성을 향상시킬 수 있음을 의미한다. 예를 들어, 새로운 타입의 Iterator가 필요하다면 새로운 Iterator 클래스를 정의하기보다는 Collection에서 새로운 메서드를 작성할 수 있다.
강한 타이핑 대 약한 타이핑
C++는 강하게 타이핑된 언어이다; 모든 변수는 컴파일러에 선언되어 특정 타입이나 특정 클래스에 속한다. 스몰토크는 더 넓은 범위의 동적 (또는 지연) 바인딩의 형태를 이용한다. 변수는 특정 클래스의 것으로 선언되지 않으며, 런타임ㅡ객체가 실제로 인스턴스화되어 변수에 의해 참조될 때ㅡ이 시작되기 전까지는 특정 타입(클래스)와 관련되지 않는다.
두 언어 모두, 어떤 단일 변수도 서로 다른 시점에서 서로 다른 클래스의 인스턴스를 가리킬 수 있다. 그러나 스몰토크에서는 전체 계층구조 내 어떠한 클래스든 그에 해당하는 인스턴스가 될 수 있지만 C++에서는 특정 기반 클래스 또는 그 클래스에서 비롯된 서브클래스의 인스턴스이다. Collection 내의 객체에서도 마찬가지다. C++의 경우 목록의 모든 객체는 특정 타입이나 클래스에(또는 그 서브클래스) 속해야 하는 반면 스몰토크의 경우 일반적으로 Collection의 어떤 클래스든 여러 종류의 인스턴스를 포함할 수 있다. 이러한 점은 많은 패턴에서 중요할 것이다. 예를 들어, 스몰토크에서 iterator는 내적으로 다형적이기 때문에 더 강력하다; 요소 타입의 리스트마다 서로 다른 타입의 Iterator 를 정의할 필요가 없다. 강한 타이핑과 약한 타이핑은 Composite, Command, Adapter 패턴에서도 비슷한 역할을 한다. 마지막 예로, C++ Adapter는 그 Adaptee의 유형을 선언해야 하므로, 선언된 클래스 또는 그의 서브클래스의 객체만 조정해야 할 것이다; 스몰토크에서는 Adapter가 Adaptee로 보낸 메시지를 포함하는 인터페이스를 가진 어떤 클래스든 Adaptee가 속할 수 있다.
약한 타이핑 언어는 유연성이 크지만 강한 타이핑 언어의 이점은 이보다 훨씬 더 많다. 예를 들어, 약한 타이핑이란 타입 안전성이 떨어짐을 의미한다. 게다가 변수가 구체적인 클래스에 속하도록 선언하는 경우 프로그램의 포괄적인 정적 분석과 컴파일 시간의 최적 활용성을 제공한다.
블록
블록은 자신이 코드를 실행하라는 메시지를 수신하기 전까지는 실행되지 않는 스몰토크 코드를 포함하는 객체이다. 즉, 코드의 블록은 보통 언어 명령문(statement)과 같이 한 가지 방법으로 연속적으로 만날 때 (sequential encounter) 발생하는 것이 아니라 블록에게 명시적으로 value 메시지를 (또는 그것의 변형체로) 전송할 때까지 연기된다. 블록은 하나의 객체이기 때문에 코드로 생성시켜 다른 객체로 전달할 수 있다; 따라서 블록은 다른 객체에 의해 평가되도록 우리가 코드의 일부를 밀쳐낼 수 있도록 해주는데, 이는 구체적 상황이나 상태가 발생할 경우로 제한될 것이다. 이러한 특징은 일반적으로 매우 유용하며 Iterator와 같은 패턴에 사용되는 것을 관찰할 것이다. 블록은 또한 특이한 코드를 한 클래스의 각 인스턴스에 부착시키는데도 효율적이다 (행위를 클래스의 모든 인스턴스에 적용하는 메서드와 반대로). 이는 Adapter 패턴의 대체 가능한 어댑터에 적용된다. 심지어 블록 구조는 컴파일러가 정해진 조건문이나 루프 구조로 제한하기보다는 언어 내에서 우리 고유의 제어 구조를 정의하도록 해준다 (Ungar & Smith, 1987). C++를 포함해 대부분 언어에는 코드의 일부를 메시지에 응답할 수 있는 first-class 객체로 만드는 구조가 없다.
반영과 메타수준의 성능
스몰토크는 스몰토크 환경 자체에 대한 정보를 얻을 수 있는 코드를 작성하도록 허용한다. 클래스와 메서드는 기반 클래스 라이브러리에 존재하므로 프로그램이 클래스 계층구조 내의 클래스 간의 관계, 최근 실행한 프로세스의 메서드, 또는 특정 클래스의 인스턴스가 이해하는 메시지를 검색하도록 허용한다. 이러한 성능은 스몰토크 프로그래머의 툴킷에서 중요한 구성요소이다. 이는 개발환경 자체의 반영적 도구를ㅡ클래스와 메서드 브라우저, 디버거ㅡ스몰토크에 내장시킨다. 그코드는 클래스 라이브러리에 포함되어 있기 때문에 추후 필요에 따라 도구를 발전 및 재정의하거나, 프로그램 이해를 위해 새로운 도구로 통합시킬 수도 있다 (예: Carroll et al., 1990).다시 한 번 언급하지만 개인의 기호에 따라 다르다. 스몰토크 사용자들은 스몰토크 환경으로 새로운 프로그래밍 도구를 구축하고 통합시키는 반영적 성능의 사용에 익숙하다.
예를 들어, 메타 수준의 구조인 doesNotUnderstand:를 이용해 우리는 좀 더 향상된 기능성으로 객체를 "꾸미는"" 목적 또는 하나의 객체가 다른 기계 또는 데이터베이스의 다른 위치에 존재하는 객체에 대해 Proxy의 역할을 위한 목적으로 만들어진 모든 메시지를 가로챈 후 그러한 외부 객체로 이 메시지의 전송 여부와 시기를 결정할 수 있다. 또한 메시지 선택기를 기호 형식으로 저장하여 내장된 perform: 메시지(와 그 변형체)를 이용해 객체로 그 메시지를 언제건 호출할 수도 있다. 이는 함수포인터를 이용해 함수를 불러오는 C++의 성능과 비슷한데, 차이가 있다면 스몰토크 버전에서는 메시지 서명의 기호적 표상을 이용할 수 있다는 것이다. Adapter, Observer, Command 패턴과 같은 스몰토크 구현에 사용된 perform:을 살펴볼 것이며, 선택기의 기호 버전을 사용할 수 있다는 사실은 Interpreter에서 중요한 역할을 할 것이다.
상속 의미론
두 언어에서 종속의 작용 방식에는 몇 가지 차이가 있다; 그 중 두 가지를 논하고자 한다. 첫째, C++는 다중 상속을 지원하는 반면 스몰토크는 클래스에 하나의 직접 슈퍼클래스만 허용한다. 다중 상속은 몇 가지 문제에 대해 즉각적인 해법을 제공한다 (예: [디자인 패턴]에서 Adapter 패턴의 클래스 어댑터 버전). 스몰토크의 경우, 그러한 문제들에 대해 대안적 해법을 고안해야 한다. 다중 상속은 프로그래밍 언어에 복잡성을 더한다; 프로그래머들은 이름의 충돌을 어떻게 처리하는지, 반복된 종속을 처리하기 위해 어떠한 규약이 준비되어 있는지 알아야 한다 (예: 동일한 슈퍼클래스에서 상속된 두 클래스로부터의 상속). 다중 상속은 복잡성과 유용성 간 균형으로 인해 현대 스몰토크 환경에서 고의적으로 배제되었다: 다중 상속의 유용성이 그 사용으로 인한 추가적 복잡성보다 크지 않다고 생각했기 때문이다 (특히 프로그램 이해적 측면에서 볼 때).
둘째, C++의 경우 함수의 동적 바인딩은 (슈퍼클래스에서 선언되고 하나 또는 그 이상의 서브클래스에 오버라이드된) 함수가 슈퍼클래스에 가상으로 선언될 때만 작용한다. Rumbaugh et al. (1991)에서 언급하였듯 이는 확장성과 점진적 재사용에 (특수화를 위해 다른 메서드를 오버라이딩하면서 상속을 통해 슈퍼클래스의 행위 일부를 재사용) 장애물이 될 수 있다. 클래스의 본래 프로그래머들이 메서드를 가상으로 선언하는 것은, 프로그래머가 아직 정의하지 않은 서브클래스가 오퍼레이션을 오버라이드할 가능성을 예상할 때 뿐이다ㅡ그리고 이러한 결정은 클래스에 정의된 모든 함수마다 이루어져야 한다 (Rumbaugh et al., 1991).
대화형 개발 환경
스몰토크 개발ㅡ주요 스몰토크 제품에서ㅡ은 항상 대화형 개발 환경에서 이루어진다. 여기에는 많은 의미가 함축되어 있는데, 실험의 용이, 검사, 프로그램 이해를 위한 도구의 이용성, 재사용 가능한 클래스와 메서드 발견이 포함된다. 설계와 관련해, 환경은 각 메서드를 저장 시 그에 대한 증분 컴파일을 제공하기 때문에 전적으로 하나의 디자인으로 결정하는 것을 피한다: 스몰토크 프로그래머들은 클래스에서 메서드를 수정하거나 추가하기 위해 전체 클래스의 소스를 재컴파일하거나 갖출 필요가 없다. 따라서 어디에 새로운 기능성이 위치해야 하는지에 관한 걱정을 덜 수 있다. 예를 들어, [디자인 패턴] 편에서 Visitor 패턴의 동기 중에는 새로운 오퍼레이션의 추가로 인하여 관련된 모든 클래스를 재컴파일해야 하는 경우를 소개한다; 따라서 이 기능성을 새로운 클래스에 위치시키고 기존 클래스의 인스턴스에 작용하도록 만드는 편이 낫다. 증분 컴파일은 주제와 관련 없는 문제를 제거함으로써 좀 더 책임 위주의 설계를 허용하므로 코드를 기능적으로 또는 논리적으로 위치시킬 수 있다.
추상 클래스, private 메서드
C++는 언어기반 특성을 포함하여 스몰토크에서는 지원하지 않는 바람직한 특성을 명시적으로ㅡ그리고 강제적ㅡ구현한다. 예를 들어 C++와 달리 스몰토크는 메서드의 privacy를 선언하고 강요하는 컴파일-시간 메커니즘을 제공하지 않는다. 프로그래머들이 메서드를 private로 작성할 수는 있지만 (예: comment) 외부 객체가 실제로 그 메서드를 호출하지 못하도록 막는 내장된 메커니즘은 없다. 마찬가지로 스몰토크에는 추상적이어야 하는 클래스의 인스턴스화를 막는 메커니즘이 없다. 스몰토크에서 C++ privacy 메커니즘에 대한 런타임 대체를 필요로 하기 때문에 위의 문제보다 더 복잡한 해법을 필요로 하는 Singleton과 같은 패턴에서 스몰토크가 어떤 역할을 하는지 살펴볼 것이다.
두 언어 간에는 이 외에도 많은 차이점이 있고, 각각의 장단점이 있다. 전체적으로 보면, C++는 효율성을 중요시하고 프로그래머 오류를 피하도록 고안되었고 (Rumbaugh et al., 1991) 스몰토크는 좀 더 유연하게 사용할 수 있도록 개발되었다. 다시 말하지만, 둘 중 하나가 어떤 면에서 "더 낫다"고 밝히려는 것이 목적이 아니다. 오히려 결론은 이렇다: 두 언어는 서로 다르기 때문에 스몰토크 프로그래머들과 C++ 프로그래머들은 디자인 패턴의 예를 서로 다르게 설명하는 경향이 있다는 것이다.