DesignPatternSmalltalkCompanion:1.3

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

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++, Smalltalk 등으로ㅡ는 시스템과 애플리케이션에 대한 사고 및 설계 방식에 영향을 미칠것이라는 사실이다. Smalltalk 전문가들과 C++ 개발자들은 서로 다른 언어로 프로그램을 구성하는데 그치는 것이 아니라, 서로 다른 언어로 말한다. 예를 들어 Smalltalk 프로그래머에게 클래스 객체는 클래스를 나타내는 진실된 스몰토크 객체인 반면, C++ 개발자들에게 클래스 객체란 사용자가 정의한 클래스의 인스턴스이다 (즉 내장된 C-언어 데이터 타입이 아니라는 의미다). 두 언어와 환경 간에 개념적으로 중복되는 부분이 상당히 많음에도 불구하고, 서로 상당히 다른 의견을 포함하고, 다른 문제를 표면화시키며, 최종적으로 프로그램 설계에 대한 서로 다른 사고를 이끌어 낸다.

반대로 설계 시점에서 목표 언어를 고려할 필요도 있다: "설계 시점에서 언어를 고려하지 않을 경우, 문제를 미해결 상태로 남길 수 있으며… 그러한 설계로 인해 형편 없는 프로그램이 되기도 한다" (Smith, 1996a). 목표 언어를 선택할 때 발생할 수 있는 제약과 기회를 고려하지 않고 설계하는 것은 실수일 것이다. 스몰토크의 경우 언어 자체뿐 아니라 클래스 라이브러리와 내장된 프레임워크도 고려해야 한다. 예를 들어, VisualWorks 의 Mode-View-Controller 프레임워크, Visual Smalltalk 의 Mode-Pane 프레임워크, IBM Smalltalk 의 Motif-style 상호작용 프레임워크를 생각해보자ㅡ각 프레임워크는 실제로 대화형 애플리케이션의 설계를 시작하기도 전에 특정 디자인의 결정이 필요로 하기도 한다.

이러한 생각을 명심하고 Smalltalk 과 C++ 이 어떻게 다른지ㅡ구체적으로 말해, 두 언어가 어떻게 설계에 영향을 미치는지ㅡ간단히 살펴보도록 하자. 이를 살펴보는 목적은, 두 언어에는 수많은 기본적 차이가 있기 때문에, 개발자들이 문제에 대해 생각하고, 해법을 설계하며, 디자인 패턴을 구현하는 방식이 서로 다를 수 밖에 없다는 우리의 주장을 뒷받침하기 위함이다(어느 한 언어가 뛰어나다는 주장을 하려는 것이 아니며, 두 언어의 비교를 통해 그 중 한 가지 언어를 선호하는 팬들을 언짢게 만들었다면 미리 사과드린다). 이 과정에서 구체적 특성의 유무에 따라 영향을 받을 일부 패턴을 언급하고자 한다.


순수한 객체지향 대 혼합 객체지향

Smalltalk 은 "순수한" 객체지향 언어이다. Smalltalk 에서 모든 계산은ㅡ가장 원시적 수준(과 일반 프로그래밍 활동과 관련이 없는 수준)을 제외한ㅡ객체로 전송하는 메시지의 결과로만 발생된다. 모든 것은 객체이며, 여기에는 숫자, 문자, 문자열과 같은 원시 데이터 타입Primitive Data Type을 포함한다. 반면 C++ 는 복합 언어로서, 절차지향의 C언어 기반에 객체지향의 특성들이 추가된 언어이다. 언어에서 제공되는 비객체지향적 특성을 이용할 수 있기 때문에, 설계자들과 프로그래머들에게서 서로 다른 사고방식을 이끌어 낸다. 예를 들어, C++ 에서는 어떠한 클래스에도 소속되지 않은 전역함수를 가질 수 있는 반면, Smalltalk 에서는 각 기능 부분의 책임을 누가 지는지를 반영해야만 한다. 복합 언어를 이용한 접근법은, 개인이 객체지향과 절차지향 패러다임의 이점을 모두 이용하는 프로그램을 사용하도록 하며, 기존의 C 코드로의 인터페이스가 훨씬 쉽다. 반면 복합 언어가 아닌 순수한 언어는 이해가 쉽다 (Rosson & Alpert, 1990).


객체로서의 클래스

모든 것을 객체로 간주하는 Smalltalk 에서 Class 는 first-class 런타임 객체를 의미한다. 이 사실은 메시지 송수신이 가능하며, 일반적으로 어떤 연산이라도 포함participating시킬 수 있음을 의미[1]한다. C++ 에서는 이런것이 불가능하다. Smalltalk 에서의 인스턴스 생성은 클래스 객체가 수행하는 업무 중의 하나지만, C++ 에는 언어 자체에 내장되어 있다. 따라서 Smalltalk 에서는 인스턴스의 생성과 동작간의 차이가 덜 명확하다: 가장 기본 형태에서 인스턴스 생성은 특수 동작(specialization of behavior)에 불과하지만, C++ 에서는 엄밀히 별도 구분된다. 대부분의 패턴이, Smalltalk 안에서 완전한 패턴(Abstract Factory, Singleton, Factory Method 패턴 등)의 형태를 한 객체를 클래스로 포함하고 있다는 것을 알 수 있다.


성숙하고 포괄적인 클래스 라이브러리

주요 Smalltalk 환경의 이점 중 하나로서, 수 년 간 다듬고 디버깅해온 거대한 기본 클래스base class 세트가 있다. 라이브러리의 광범위한 사용 결과, 라이브러리에 포함된 저수준의 추상 데이터 타입마저 시간이 지나면서 향상된 덕분에 광범위한 성능을 가지게 되었다. 이렇게 광범위한 기능성은 특정 설계 고려사항은 물론이며, 심지어 일부 디자인 패턴 구현에 대한 고려사항조차 없애버린다. 예를 들어, Smalltalk 에서는 기본 Collection 클래스가 자체 반복iteration 메서드를 제공하기 때문에. 내부 반복자Iterator를 설계하거나 구현할 필요가 없다. Composite 를 포함한 다른 패턴들도 광범위한 기본 클래스 라이브러리의 기능성을 재사용하기 때문에 혜택을 받을 수 있다. '모든 것은 객체다'라는 주제로 다시 돌아가보면, 숫자, 문자열, Collection과 같은 추상 데이터 타입은 언어 자체에 내장된 블랙박스 데이터 타입이 아니라, 기본 클래스 라이브러리에서 사용자가 수정이 가능한 클래스로서 구현된다. 즉 이러한 클래스 내에서 사용자만의 메서드를 정의함으로써 객체의 기능성을 향상시킬 수 있음을 의미한다. 예를 들어, 새로운 타입의 반복자가 필요하다면 새로운 Iterator 클래스를 정의하기보다는 Collection 에서 새로운 메서드를 작성할 수 있다.


강한 타이핑 대 약한 타이핑

C++ 는 강한 타이핑 유형의 언어이다; 모든 변수는 컴파일러에 선언되며, 특정 타입이나 특정 클래스에 속한다. Smalltalk 는 더 넓은 범위의 동적 (또는 지연) 바인딩의 형태를 이용한다. 변수는 특정 클래스의 것으로 선언되지 않으며, runtimeㅡ객체가 실제로 인스턴스화되어 변수에 의해 참조될 때ㅡ으로 시작되기 전까지는 특정 타입(클래스)와 관련되지 않는다.

두 언어 모두, 어떤 단일 변수도 서로 다른 시점에서 서로 다른 클래스의 인스턴스를 가리킬 수 있다. 그러나 Smalltalk 에서는 전체 계층구조 안의 어떠한 클래스라도 그에 해당되는 인스턴스가 될 수 있지만, C++ 에서는 특정 기반 클래스 또는 그 클래스에서 비롯된 하위클래스의 인스턴스가 된다. Collection 내의 객체에서도 마찬가지다. C++ 의 경우 목록의 모든 객체는 특정 타입이나 클래스에(또는 그 하위클래스) 속해야 하는 반면, Smalltalk 의 경우에는 일반적으로 Collection 의 어떤 클래스라도 여러 종류의 인스턴스를 포함할 수 있다. 이러한 특징은 많은 패턴에서 중요하다. 예를 들어, Smalltalk 에서 Iterator 는 내부가 다형적이기 때문에 더 강력하며, element type 마다 서로 다른 유형의 Iterator 를 정의할 필요가 없다. 강한 타이핑과 약한 타이핑은 Composite, Command, Adapter 패턴에서도 비슷한 역할을 한다. 마지막 예로서, C++ 의 Adapter 는, 해당 Adaptee 의 유형을 선언해야 하기 때문에, 선언된 클래스 또는 그 하위클래스의 객체만 조정해야 하지만, Smalltalk 에서는 Adapter 가 Adaptee 로 보낸 메시지를 포함하는 인터페이스를 가진 어떤 클래스에도 Adaptee 는 소속될 수 있다.

약한 타이핑 언어는 유연성이 크지만 강한 타이핑 언어의 이점은 이보다 훨씬 더 많다. 예를 들어, 약한 타이핑이란 타입 안전성이 떨어짐을 의미한다. 게다가 변수가 구체적인 클래스에 속하도록 선언하는 경우, 프로그램의 포괄적인 정적 분석과 컴파일 시간의 최적 활용성을 제공한다.


블록(Block)

블록block은 자신이 코드를 실행하라는 메시지를 수신하기 전까지는 실행되지 않는, Smalltalk 코드를 포함하는 객체이다. 즉, 코드를 포함한 블록은, 일반적으로 언어 명령문(statement)처럼 한 가지씩의 방법이 연속적으로 만나면 발생(sequential encounter)하는 것이 아니라 블록에게 명시적으로 value 값 메시지를 (또는 그것의 변형체로) 전송할 때까지 대기된다. 블록은 하나의 객체이기 때문에 코드로 생성시켜 다른 객체로 전달할 수 있으며, 따라서 블록은 다른 객체에 의해 평가evalute되도록 사용자가 코드의 일부를 밀어낼 수 있도록 해주는데, 이는 구체적인 상황이나 상태가 발생할 경우로 제한된다. 이러한 특징은 대부분의 경우 매우 유용하며 Iterator 와 같은 패턴에 사용되는 것을 확인하게될 것이다. 블록은 또한 특이한 코드를 하나의 클래스의 각 인스턴스에 연결시키는데도 효율적이다(행위를 클래스의 모든 인스턴스에 적용하는 메서드와 반대로). 이는 Adapter 패턴의 대체 가능한 어댑터에 적용된다. 심지어 블록 구조는 제어구조를 컴파일러가 정해진 조건문이나 루프 구조로 제한하기보다는 언어 내에서 우리 고유의 제어 구조를 정의하도록 해준다 (Ungar & Smith, 1987). C++를 포함해 대부분 언어에는 코드의 일부를 메시지에 응답할 수 있는 first-class 객체로 만드는 구조가 없다.


반영(refelection)과 메타수준의 성능

스몰토크는 스몰토크 환경 자체에 대한 정보를 얻을 수 있는 코드를 작성하도록 허용한다. 클래스와 메서드는 기본 클래스 라이브러리에 존재하므로 프로그램이 클래스 계층구조 내의 클래스 간의 관계, 최근 실행한 프로세스의 메서드, 또는 특정 클래스의 인스턴스가 이해하는 메시지를 검색하도록 허용한다. 이러한 성능은 스몰토크 프로그래머의 툴킷에서 중요한 구성요소가 된다. 이는 개발환경 자체의 반영적 도구를ㅡ클래스와 메서드 브라우저, 디버거ㅡSmalltalk 에 내장시킬 수 있게 한다. 이런 코드들은 클래스 라이브러리에 포함되어 있기 때문에 추후 필요에 따라 도구를 개량 및 재정의하거나, 프로그램의 이해를 위해 새로운 도구로 통합시킬 수도 있다(예: Carroll et al., 1990). 다시 한 번 언급하지만 개인의 기호에 따라 다르다. Smalltalk 사용자들은 Smalltalk 환경으로 새로운 프로그래밍 도구를 구축하고 통합시키는 반영적 성능의 사용에 익숙하다.

예를 들어, 메타 수준의 구조인 doesNotUnderstand: 를 이용해서, 사용자는 좀 더 향상된 기능성으로 객체를 "꾸미는" 목적, 또는 하나의 객체가 다른 기계 또는 데이터베이스의 다른 위치에 존재하는 객체에 대해 Proxy 의 역할을 하기 위한 목적으로 만들어진 모든 메시지를 가로챈 뒤에, 원하는 외부 객체로 이 메시지의 전송 여부와 시기를 결정할 수 있다. 또한 메시지 선택기selector를 기호 형식으로 저장해서, 내장된 perform: 메시지(와 그 변형체)를 이용해 객체로 그 메시지를 언제라도 호출할 수도 있다. 이는 함수 포인터를 사용해서 함수를 호출하는 C++ 의 기능과 유사합니다. 다른점이라면 Smalltalk 버전에서 메시지 서명의 기호symbolic 표현을 사용할 수 있다는 것입니다. Adapter, Observer, Command 같은 패턴의 Smalltalk 구현에 사용되는 perform: 이 표시되며 선택기의 기호 버전을 사용할 수 있다는 사실이 인터프리터에서 역할을 수행하게 됩니다.


상속 의미론(Inheritance Semantics)

2 개의 프로그래밍 언어에서 상속의 작용 방식에는 몇 가지 차이가 있는데, 그 중에 두 가지에 대해 알아보자. 첫째, C++ 는 다중 상속을 지원하지만, Smalltalk 는 클래스에 하나의 직접 상위클래스만 허용한다. 다중 상속은 몇 가지 문제에 대해 즉각적인 해법(예: [디자인 패턴]에서 Adapter 패턴에 대한 class adapter 버전)을 제공한다. Smalltalk 의 경우, 그러한 문제들에 대한 해법을 대안으로 마련해야 한다. 다중 상속은 프로그래밍 언어에 복잡성을 더한다; 프로그래머들은 이름의 충돌을 어떻게 처리하는지, 반복된 종속을 처리하기 위해 어떠한 규약이 준비되어 있는지를 알아야 한다(예: 동일한 상위클래스에서 상속된 두 클래스로부터의 상속). 다중 상속은 복잡성과 유용성 사이의 균형으로 인해 현대 Smalltalk 환경에서는 고의적으로 빠져있다. 다중 상속의 유용성이 그 사용으로 인한 추가적 복잡성보다 크지 않다고 생각했기 때문이다(특히 프로그램의 이해적 측면에서 볼 때).

둘째, C++ 의 경우 함수의 동적 바인딩(상위클래스에서 선언되었으며, 하나 또는 그 이상의 하위클래스에 오버라이드된 경우)은 함수가 상위클래스에 가상으로 선언된 경우에만 작동된다. Rumbaugh et al. (1991)은 이런 특성이 확장성 및 점진적 재사용(특수화를 위해 다른 메서드를 오버라이딩하면서 상속을 통해 상위클래스의 행위 일부를 재사용)에 장애물이 될 수 있다고 지적했다. 처음으로 클래스[2]를 작성한 프로그래머가 메서드를 가상으로 선언하는 유일한 이유는, 아직 정의되지 않은 하위클래스에서 해당 연산을 오버라이드할 가능성이 있을때 뿐이며, 이런 방식의 선언은 클래스에 선언되는 각 함수별로 적용된다[3].(Rumbaugh et al., 1991).


대화형 개발 환경

주요 Smalltalk 제품에서의 Smalltalk 개발은 항상 대화형 개발 환경에서 이루어진다. 여기에는 많은 의미가 함축되어 있는데, 쉬운 실험, 검사, 프로그램 이해를 위한 도구의 이용성, 재사용 가능한 클래스와 메서드 발견등이 있다.. 설계와 관련해서, 개발 환경은 각 메서드를 저장할때, 대상이 되는 메서드에 대한 증분 컴파일을 제공하기 때문에 하나의 설계로 결정되어 버리는걸 피할 수 있으며, Smalltalk 프로그래머들은 클래스에서 메서드를 수정하거나 추가하기 위해 전체 클래스의 소스를 재컴파일하거나 갖출 필요가 없다. 따라서 대체 어디에 새로운 기능이 위치해야 하는지에 관한 걱정을 덜 수 있다. 예를 들어, [디자인 패턴] 에서 Visitor 패턴을 사용하는 이유 대해, 새로운 동작의 추가로 인해 관련된 모든 클래스를 재컴파일해야 하는 경우를 소개하는데, 이런 경우라면 기능을 새로운 클래스에 위치시키고 기존 클래스의 인스턴스에 작용하도록 만드는 편이 낫다. 증분 컴파일은 주제와 관련 없는 문제를 제거함으로써 책임 중심의 설계를 허용하기 때문에, 코드를 기능적으로 또는 논리적으로 위치시킬 수 있다. 예를 들어, [디자인 패턴] 에서는, Visitor 패턴을 적용하는 이유중에 하나로서, 새로운 동작의 추가로 인해 관련된 모든 클래스를 재컴파일해야 하는 경우를 소개하는데, 이런 경우에서 C++ 는 기능을 새로운 클래스에 위치시키고 기존 클래스의 인스턴스에 작용하도록 만드는 편이 낫다. Smalltalk 에서 제공되는 증분 컴파일은 주제와 관련 없는 문제를 제거함으로써 코드를 기능적으로 또는 논리적으로 위치시킬 수 있기 때문에, 책임 중심의 설계를 허용한다.


추상 클래스, private 메서드

C++ 는 언어기반 특성을 포함해서 Smalltalk 에서는 지원하지 않는 바람직한 특성을 명시적으로ㅡ그리고 강제적ㅡ구현한다. 예를 들어 C++ 와 달리 Smalltalk 는 메서드의 privacy 를 선언하고 강요하는 compile-time 메커니즘을 제공하지 않는다. 프로그래머들이 메서드를 private 으로 작성할 수는 있지만 (예: comment) 외부 객체가 실제로 그 메서드를 호출하지 못하도록 막는 내장된 메커니즘은 없다. 비슷한 경우로 Smalltalk 에는 추상적이어야 하는 클래스의 인스턴스화를 막는 메커니즘이 없다. Smalltalk 에서 C++ privacy 메커니즘에 대한 런타임 대체runtime substitute를 필요로 하기 때문에 위의 문제보다 더 복잡한 해법을 필요로 하는 Singleton 과 같은 패턴에서 Smalltalk 이 어떤 역할을 하는지 살펴볼 것이다.

두 언어 간에는 이 외에도 많은 차이점이 있고, 각각의 장단점이 있다. 전체적으로 보면, C++ 는 효율성을 중요시하고 프로그래머가 오류를 피하도록 고안되었으며(Rumbaugh et al., 1991) Smalltalk 는 좀 더 유연하게 사용할 수 있도록 개발되었다. 다시 말하지만, 둘 중 하나가 어떤 면에서 "더 낫다"고 하고싶은건 아니다. 오히려 중요한 부분은 이 2개의 프로그래밍 언어가 서로 다르다는 것이며, Smalltalk 프로그래머와 C++ 프로그래머는 디자인패턴을 다르게 설명할 가능성이 있다는것이다. 따라서 이 책의 목표는 디자인 패턴의 23가지 패턴에 대한 Smalltalker 의 관점을 제공하는 것이다.


Notes

  1. Smalltalk 에서 모든 클래스는 객체의 unique instance 라는 사실
  2. 일반적인 프로그래밍에서 부모클래스가 되는 클래스
  3. C++ 에서는 클래스 자체에는 virtual keyword 가 없으며(상속을 받으면 되는거니), 각 메서드 에서만 virtual 을 선언할 수 있기때문. 왜 이런 명시적 선언이 필요한가 하면, 첫번째로, 상속될 가능성이 있는 놈의 소멸자를 virtrual 로 선언하는 이유는, 객체 소멸의 순서를 컴파일러가 지킬 수 있도록 해주는것. 두번째는, VT(Virtual Table)을 생성할지를 컴파일러가 빨리 계산할 수 있기 때문이며, 그 외에도 virtual abc() = 0; 같은 순수 가상 함수를 만들어서, 상속받은 클래스에서 반드시 해당 함수를 구현하도록 강제할 수도 있고, 오버로드와 오버라이드를 명시적으로 구분할 수도 있기 때문.