TheArtandScienceofSmalltalk:Chapter 12

From 흡혈양파의 번역工房
Jump to: navigation, search
제 12 장 스몰토크를 위한 디자인하기

Contents

스몰토크를 위한 디자인하기

이번 장의 제목을 쉽게 바꾸자면 '객체 찾기'가 되겠다. 객체가 무엇인지는 모두들 잘 이해하지만 스몰토크에서 애플리케이션을 구현하기 위해 어떤 종류의 객체를 디자인해야 할 것인지는 결정하기가 힘들다. 사실 스몰토크와 같은 개발 시스템과 애플리케이션에 대한 요구조건들이 주어졌을 때 경험이 부족한 객체 지향 개발자들이 직면해야 하는 가장 까다로운 업무가 바로 '객체 찾기'가 된다. 그러면서도 개념과 기법의 학습은 항상 프로그래밍 언어나 툴을 학습하는 것보다 더 까다롭다.


그래도 자신감을 얻길 바란다. 자신이 숙련된 절차적 언어 프로그래머라면 정교한 프로그램을 생성하기 위해 프로시저에 어떤 기능을 넣어야 하는지 쉽게 결정할 수 있을 것이다. 프로그래밍을 처음 시작할 때는 이러한 분할 작업이 꽤 힘들게 느껴질지도 모른다. 하지만 어떻게 해서든 익숙해져 결국은 전문가가 된다. 프로그램을 여러 프로시저로 나누듯, 프로그램을 객체로 나누는 기술 또한 매우 선천적인 소질이 될 수 있다. 애석하게도 객체 지향 디자인(OOD)에서 그러한 기술을 습득하는 데에는 시간과 경험이 필요하다. 이번 장의 목표는 이러한 기술들을 좀 더 빠르게 습득하도록 도와주는 데에 있다.


하나의 장에서 OOD의 전체 범위와 깊이를 모두 다루길 바랄 수는 없다. 이 범위를 모두 다루려면 이 책의 전체 또는 그 이상의 책이 필요하다. 대신 이번 장에서는 스몰토크에서 시스템을 디자인하는 것이 어떤 느낌인지를 보여주려 한다. 우선 '일반적(conventional)' 디자인과 어떻게 다른지, 주요 고려사항은 무엇인지, OOD가 수반하는 업무가 무엇인지 고려한 후 (애석하게도 마력은 찾을 수 없을 것이다), 상속을 어떻게 훌륭하게 사용하는지를 살펴봄으로써 마무리 지을 것이다. 완벽하진 않지만 몇 가지 예를 들어 이론을 설명할 것인데, 훌륭한 스몰토크 디자인의 예를 더 살펴보고 싶다면 시스템 라이브러리 내 클래스를 쳐다보는 것만으론 부족할 것이다.


스몰토크를 위한 디자인은 어떻게 다른가

스몰토크 시스템을 위해 디자인하는 과정은 일반적 디자인의 과정과 어떻게 다른지 인식하는 것이 중요하다. 주요 차이들은 명백하다. 재래식 프로그래밍의 경우, 데이터의 구조에서 실행되는 코드의 구조를 데이터의 구조와 별개로 간주할 수 있다. OOP의 성질은 이 두 개의 개념이 서로 관련되어(묶여) 있음을 의미한다. 스몰토크의 경우 (가령, C++는 해당하지 않음) 모든 코드 조각은 클래스와 연관되어야 한다. 이 자체만으로 초보자들에겐 문제가 된다.


스몰토크에서 개발이 두음 운율(alliterative) 과정이라는 사실은 여러 번 논한 바 있다. 즉, 분석 기간 다음에 디자인 기간이 따라오고, 그 다음으로 디자인이나 분석 작업에 착수하기 전에 프로토타입 구현이 따라올 수 있다는 의미다. 디자인을 다른 장에서 따로 고려한다고 해서 반복적 개발이나 래피드 프로토타이핑(rapid-prototyping)이 스몰토크 프로그래밍의 선택적 기능이라고 확신하지는 마라.


이러한 반복적 특징은, 기존 아키텍처의 이용 또는 디자인의 형식적 프로세스를 이용하고 싶을 경우, 특히 그 프로세스나 구조가 재래식 프로그래밍 언어를 위해 생성된 경우 매우 유의해야 함을 의미한다. 기존 방법론들 중 일부는 폭포수(waterfall) 접근법을 강조한다. 즉 시스템이 하향으로 완전히 디자인된 후 상향으로 구현된다. 여기엔 훌륭한 이유가 있다. 재래식 언어를 이용하면 디자인 실수를 수정하는 데 비용이 많이 든다는 것이다. 하지만 폭포수 개발에 유용한 기법들을 반복적 개발에 이용할 경우 실패하는 경향이 있다. 이는 다음 프로세스 단계가 시작되기 전에 이전 프로세스 단계를 완료해야만 하기 때문이다-스몰토크에 가능한 기회주의적 접근법에는 쓸모가 없다.


일부 기법들은 언어 독립을 강조하는 경향도 있다. 후에 C, C++, Pascal 등 언어에서 구현할 수 있는 디자인을 생성하는 것이다. 하지만 스몰토크를 이용 시 주요 혜택들 중 하나는 클래스 라이브러리에서 코드를 재사용할 수 있다는 점이다. 이를 위해선 디자인 과정 중에 당신이 스몰토크에서 구현할 것이란 사실을 고려해야 한다. 이를 어길 시 결국 기존 클래스 라이브러리가 구성된 방식에 어긋나는 클래스를 명시하게 된다. 그러면 결국 필요한 것보다 훨씬 더 많은 기능을 구현하도록 강요받는다. 목표 언어에 대한 디자인 의존성을 받아들이기 쉽지 않다면, 디자인을 할 당시 보트를 만들기 위해서는 어떤 자재를 계획하는지 알고 있다고 생각해보라. 실제 나무처럼 스몰토크 클래스 라이브러리에도 '결'이 있다. 디자인이 그 결을 가로지르지 않고 결대로 따라간다면 구현 시 좀 더 인생이 수월해질 것이다.


OOD와 스몰토크에 훌륭하고 좀 더 형식적인 방법론들도 물론 많다. 여기엔 객체 지향시스템의 모든 생명주기 부분들을 다루는 일반 객체 지향 방법론인 Hewlett Packard의 Fusion 과, 스몰토크에 한정된 ParcPlace의 객체 행위 분석과 디자인(Object Behavior Analysis and Design)이 있다. 소, 중간 규모 프로젝트가 아닌 프로젝트를 진행 중이거나, 디자인에 대한 자신의 접근법에서 특히 엄밀성이나 추적성이 염려된다면 위의 기법들 중 하나를 고려해볼 것을 권한다. 이번 장의 나머지 부분에서는 많은 OOD 접근법들이 공유하는 개념을 일상적인 말로 설명하고, 스몰토크에 적용하는 방법을 고려해보겠다. 이러한 과정은 대부분의 스몰토크 프로그래머들이 스몰토크 프로그램을 디자인하는 비형식적 프로세스에 참여 시 행하거나 생각하는 것들이다.


디자인 고려사항

재래식 언어를 위해 디자인할 때는 '어떤 절차를 가져야 하는가?', '그러한 절차들은 무엇을 리턴해야 하는가?', '매개변수로서 무엇을 전달해야 하는가?'와 같은 질문을 해야 한다. OOD를 할 때는 '해당 메서드는 어떤 클래스에 있어야 하는가?', '그 클래스를 서브클래싱해야 하는가, 아니면 캡슐화해야 하는가?'라는 추가 질문도 더해야 한다. OOP는 이러한 문제를 생각하도록 강요하는데, 사실상 이러한 질문들을 통해 더 나은 디자인 작업을 하도록 장려한다.


그렇다고 OOP가 형편없는 디자인이 생성되지 못하도록 막는 것은 아니다. 재래식 언어에서와 달리 객체 지향 언어에서 요구조건을 충족하지 못해 형편없이 구조화되고, 관리가 불가능하며, 버그에 시달리는 애플리케이션도 쉽게 생성된다. 하지만 OOP에서 훌륭한 디자인과 형편없는 디자인을 구성하는 것이 무엇인지 인식하고, 훌륭한 디자인을 생성하기 위한 기술을 습득함으로써 주요 결함을 피할 가능성이 있다. 디자인 과정 중에 유념해야 할 중요한 사항을 몇 가지 살펴보자. 여기서 언급하면 잘 이해되지 않을 수 있으나 본장의 나머지 부분을 읽으면서 자신만의 디자인을 시도한다면 인식에 도움이 될 것이다.


훌륭한 디자인의 혜택을 인식하기

훌륭한 디자인은 수많은 혜택을 제공한다. 더 높은 의존성, 더 나은 유지성, 양호한 코드 재사용성, 빠른 구현, 높은 성능, 낮은 자원 요구조건은 모두 훌륭한 디자인이 가져오는 혜택에 속한다. 하지만 때로는 이러한 속성들이 서로 상극될 수 있으므로 (예: 빠른 구현은 낮은 코드 재사용성을 암시하기도 한다) 자신이 가장 소중하게 생각하는 혜택이 무엇인지 안다면 이득이 되겠다.


인터페이스부를 구현부와 별개로 간주하라

클래스의 인터페이스부는 그것이 프로그래머에게 제공하는 기능 집합을 프로그래머에게 보여주고 이용 가능하게 만드는 방식을 의미한다. 스몰토크에서 이것은 메서드 집합을 의미하는데, 이는 프로그래머가 클래스를 통해 호출하거나 클래스의 서브클래스에 의해 인스턴스 변수와 함께 상속될 수 있다. 캡슐화를 최대로 활용하기 위해서는 이러한 인터페이스부를 (클래스가 이해하는 '프로토콜'에 대한 스몰토크의 용어) 기능의 구현부와 별개로 간주해야 한다. 구현부는 클래스에 private한 채로 유지되어야만 클래스 프로그래머가 결정한 바에 따라 수정 및 개선될 수 있다.


복잡성을 숨겨라

위와 동일한 말인데 다르게 표현한 것이다. 클래스에 대해 양호하고 간단하며 보편적인 인터페이스부를 제시하고 클래스가 그 기능을 구현하는 방법의 복잡성을 클래스에서 숨길 수 있다면 그렇게 하라. 이 방법을 통해 클래스의 사용이 쉬워질 것이며, 자유롭게 구현부를 독립적으로 만지작거릴 수 있다.


클래스간 의존성을 최소화하라

모듈화(modularity)는 클래스들이 서로에 관해 아는 바가 적을수록 크게 향상된다. 이는 다른 클래스에 영향을 미치지 않고 클래스를 변경할 수 있도록 해준다. 특히 클래스가 서로의 구현부에 대해 알아서 안 되는 경우에 그렇다. 물론 서로에 대해 아는 협력하는 클래스 집합이 필요한 때도 종종 있다. 이런 경우 클래스 집단을 마치 '모듈'인 것처럼 간주하면 된다. 해당 모듈 내 클래스들은 서로 간에 사용되는 private 프로토콜과 구별된 public 인터페이스부를 표현해야 한다.


사용자 인터페이스를 애플리케이션 로직과 분리된 채 유지하라

클래스의 인터페이스부를 구현부와 별개로 유지하라는 위의 지침과 혼동하지 마라. 사용자 인터페이스를 애플리케이션 로직으로부터 분리하는 것은 기본적인 스몰토크 원칙으로, 다시 말하지만 모듈화를 최적화하기 위함이다. MVC 아키텍처의 원칙을 이해하고 준수한다면 자동으로 사용자 인터페이스를 애플리케이션 로직으로부터 분리시킬 수 있을 것이다. 하지만 주의하지 않으면 섞이기가 너무 쉬우므로 MVC를 확실하게 이해하지 못하겠다면 다시 8장과 9장을 살펴보라.


복잡한 알고리즘 제거하기

재래식 프로그래밍과 마찬가지로 복잡한 알고리즘을 로직 조각으로 나누기를 시도해야 한다. 이는 코드의 작성, 시험, 디버깅을 쉽게 만들뿐 아니라 독립적으로 재사용할 수 있는 조각들을 제공하기도 한다. 하지만 앞의 고려사항에서 보았듯 알고리즘을 여러 클래스 주위에 분배 시 주의해야 한다. 이는 클래스들이 서로의 구현부에 관한 지식에 너무 의존하도록 만들 수도 있다.


복합 변수 제거하기

객체 상태의 여러 측면을 하나의 변수로 '인코딩'하지 않는다. 이를 어길 시 추후 개발과 재사용에 해가 된다. 객체의 상태에 대해 각 측면마다 하나의 변수를 사용하라.


특수 목적의 클래스는 가능한 적게 생성하라

단일 클래스가 많은 인스턴스를 가질 수 있다는 사실이 스몰토크에서 재사용의 목적을 달성하는 데 있어 핵심임을 기억하라. 애플리케이션에 특수 목적을 가진 클래스를 많이 생성해야 한다고 생각된다면 자신이 모델링하려는 일반 개념들을 제대로 이해했는지를 다시 생각할 필요가 있다.


클래스 로드맵을 염두에 두어라

스몰토크를 위한 디자인을 할 때는 스몰토크 언어에 대한 디자인에 그치는 것이 아니라 기존에 있는 (그것도 매우 큰) 코드 본문(body of code)의 연장부분을-클래스 라이브러리-디자인하는 것이기도 하다. 코드 본문을 가능한 한 많이 사용하도록 하라. 결국 Object 이외의 클래스로부터 상속받게 되더라도 특정 환경에서 상주하게 될 클래스를 디자인하게 될 것이다. 디자인을 기존 코드와 잘 호환되고 기능하게 만들면 구현 시 훨씬 작업이 수월해질 것이다. 다시 말해, 스몰토크 '스타일'을 사용하라는 말이며, 이에 관해서는 다음 장에서 상세히 논하도록 하겠다.


간단하게 유지하라

당연한 말이지만 기억할 필요가 있다. 디자인을 계속해서 반복함으로써 더욱 더 단순해지겠지만, 그것이 제공하는 기능은 더 보편적으로 변하고 그에 따라 더 강력해진다.


최고 수준에서 보면 디자인은 시스템의 바람직한 행위에 대한 이해부터 시스템 구현부 명시까지의 과정이라 할 수 있다. 이것이 순전히 기계적 과정이라면 그것을 실행하는 컴퓨터 프로그램을 작성하면 된다. 불행히도 (혹은 이것을 직업으로 삼는 사람에겐 다행히도) 디자인은 디자이너의 엄청난 재량과 판단을 요구한다.


객체 지향 디자인은 많은 업무 실행을 수반한다. 각 업무는 앞의 업무 중에 내린 결정에 따라 크게 좌우된다. 업무를 수행하면서 내린 결정은 앞에서 내린 결정을 변경하거나 영향을 미치기도 한다. 따라서 디자인 자체도 반복적 과정이다. 하지만 우리는 OOD를 여러 업무로 분할하여 각각을 별개로 간주할 수 있다. 실제로 이러한 업무는 상황에 따라 통합, 제거, 재정렬, 반복, 또는 변경할 수 있음을 기억하라. 아래 실린 내용을 그대로 따를 필요는 없으며, 그저 구성 요소로 취급하면 된다. 그 중에서 몇 가지는 추후 자세히 살펴볼 것이며, 업무 리스트는 본장 마지막에 요약하고자 한다.


필요한 기능성을 결정하라

해당 업무는 재래식 프로그래밍에서 상응하는 업무와 약간 다르다. 구체화(specification)는 (상황에 따라 다를 수 있으며, 공식 문서부터 머릿속 생각까지 무엇이든 가능하다) 무엇을 성취해야 하는지 말해줄 것이다. 구체화는 그것을 어떻게 성취할 것인지 결정하는 첫 번째 단계다. 어떤 종류의 데이터를 보관해야 할 것인가? 데이터를 어떤 방식으로 조작해야 할 것인가? 사용자 인터페이스는 어떤 모양을 할 것인가? 다시 말하자면, '애플리케이션 도메인의 요구조건은 어떻게 컴퓨팅 도메인 내 요구조건으로 매핑할까'가 된다.


이를 이해시키는 방법에는 여러 가지가 있지만 '시나리오'를 사용하는 방법이 가장 효과가 있다. 요구조건의 특정 측면을 골라 그 요구조건을 지원하려면 정확히 어떤 기능이 제공되어야 하는지를 결정하라. 요구조건의 중요한 측면마라 이를 실행하라.


어떤 객체가 기능성을 제공할 것인지 찾아라

당신이 제공하고자 하는 기능성이 확실해지면 어떻게 그것을 구현할 것인지 고려해볼 수 있다. 이 작업은 '객체 찾기'를 목적으로 하는 첫 번째 작업이다. 객체는 두 가지 장소로부터 발생한다. 기존 클래스의 인스턴스이거나, 해당 애플리케이션용으로 작성된 클래스의 인스턴스가 된다.


당신이 사용하게 될 기존 클래스 대부분은 표준 클래스 라이브러리에서 비롯될 것이다. 나머지는 이전에 당신이 작성하였거나 다른 사람들로부터 얻은 클래스일 것이다. 이러한 클래스가 실제로 당신이 원하는 작업을 수행할 것인지 확실히 한다면 해당 객체들이 관련된 아래의 작업들 중 다수를 피하도록 해줄 것이다.


당신이 필요로 하는 기능을 구현하는 기존 클래스를 찾을 수 없다면 스스로 확인하고 디자인하여 구현해야 할 것이다. 객체를 확인하는 것이 이 과정에서 가장 까다로운데, 이를 어떻게 처리하는지 간략하게 논해보자.


객체를 클래스로 그룹화하기

자신의 애플리케이션이 필요로 할 새로운 객체 유형을 작업하는 동안에는 클래스와 관련해 사고하는 수밖에 없다. 해당 작업을 이렇게 따로 설명하는 이유는 자신의 시스템에 여러 객체들 간 공통성을 찾는 과정을 반복하도록 장려하기 위해서다. 당신은 '그 두 개의 객체가 실제로 동일한 클래스의 인스턴스들인가?'와 같은 질문을 해봐야 한다.


객체들이 어떻게 서로 관련될 것인지 결정하라

객체들은 여러 중요한 방식으로 서로 관련된다. 그들은 서로에 대한 참조를 유지하고, 서로 메시지를 전송하며, 서로에게서 상속된다. 상속은 뒤로 미루더라도 지금은 어떤 객체들이 서로에 대해 알고, 그들 간에 어떤 메시지가 전송되는지를 알아볼 필요가 있겠다.


이러한 관계를 고려하면서 당신은 특정 유형의 다이어그램을 사용하면 유용하겠다고 생각할 것이다. 본인이 편한 다이어그램 유형이 무엇이든 사용해보라. 원한다면 표준 표기법을 사용해도 무관하고, 고유의 표기법을 사용해도 좋다. '객체 관계' 다이어그램, '메시지 교환' 다이어그램, 플로우차트(흐름도) 등의 다이어그램을 이용할 수 있겠다. 객체의 구조에 관한 정보를 전달하는 데 다양한 다이어그램 타입을 이용하는 방법을 다시 살펴보려면 제 1부로 돌아가라. 규칙에 사로잡히지 마라!


객체 관계 또는 이와 유사한 다이어그램을 그릴 때 매우 조심해야 할 한 가지는 서로 다른 관계 유형을 구별하는 것이다. 이는 특히 '~를 포함하다' 또는 '~로 참조하다' 와 '~는 ...의 인스턴스다'와 '~는 ...의 서브클래스다' 는 서로 상반된다는 사실에 적용된다. 이는 누군가와 디자인에 대해 논할 때 서로 뜻이 엇갈리는 결과로 빠지기 쉬우므로 조심해야 한다.


클래스에 대한 인터페이스부 디자인하기

어떤 종류의 객체를 구현할 것인지, 그러한 객체들이 서로 어떻게 관련될 것인지 결정했으니 객체에 대한 인터페이스부를 디자인하는 작업은 충분히 간단할 것이다. 즉, 어떤 클래스에 어떤 메서드를 구현할 것인지, 이러한 메서드는 어떤 매개변수를 취할 것인지, (어떻게 구현되는지가 아니라) 어떤 일을 실행할 것인지, 리턴값은 무엇이 될 것인지 결정하는 것을 의미한다. 인터페이스부는 가능한 한 보편적(general)으로 유지하도록 하라.


클래스의 구현부 디자인하기

필요한 인스턴스 변수를 결정하는 작업과, 클래스에 대한 인터페이스부를 구현하게 될 메서드를 디자인하는 작업을 의미한다. 굳이 이것을 인터페이스부의 디자인으로부터 구분하는 이유는, 앞서 소개한 디자인 고려사항들 중 하나-인터페이스부를 구현부로부터 구분하라-를 강조하기 위함이다. 하지만 지금이야말로 스몰토크에서 디자인이 코딩으로 합쳐지는 때이다. 인터페이스부를 명시하고 복합 연산을 제거하는 데 성공했다면, 기능성을 작동하는 스몰토크로 바꾸는 작업은 상대적으로 수월할 것이다.


상속을 이용해 구현부 그룹화하기

상속은 스몰토크에서 클래스를 디자인할 때 고려해야 하는 마지막 사항이다. 비록 양호한 재사용성을 확보하는 데 매우 중요한 핵심 요인이긴 하지만 클래스가 실제로 무엇을 할 것인지, 어떻게 구현될 것인지를 알기 전까진 클래스들이 어떻게 각자로부터 상속될 것인지 생각할 수가 없기 때문이다. 물론 상속을 고려하는 순간 좀 더 '상속 가능'하게 만들기 위해 클래스의 인터페이스부와 구현부의 세부내용을 보고 수정하길 원할 것이다. 그 정도는 괜찮으며, 디자인의 반복 과정에서 일부에 해당할 뿐이다.


디자인 과정에서 상속에 너무 일찍 휘말리게 만드는 유혹에 저항해야 한다. 이는 경험이 많이 쌓여 쉽게 '앞을 내다볼 수' 있을 때까지는 미루는 것이 좋다. 이러한 정신에 맞춰 이번 장에서는 상속을 마지막으로 논하고자 한다.


객체 확인하기

이것이야말로 OOD에서 핵심 부분으로, 대부분의 사람들에겐 스몰토크로 작업 시 가장 까다롭다고 느끼는 부분이기도 하다. 다행히 시간이 지나면 자연스럽게 익히게 된다. 그동안 몇 가지 지침을 살펴보자.


지금쯤이면 스몰토크에서 모든 것은 객체라는 사실을 알고 있을 것이다. 하지만 다른 수많은 것들 또한 객체가 될 수 있음을 기억하라. 스몰토크 객체로서 모델링할 수 있는 것들의 종류를 예로 보고 싶다면 시스템 클래스 라이브러리를 살펴보는 데 시간을 들여라. 한 가지 경험에 근거한 법칙을 소개하자면, 무언가에 관해 이야기할 수 있고 그것이 자신의 시스템에 중요하다면 아마 그것은 객체일 것이라는 사실이다. 하지만 one object=one idea (하나의 객체=하나의 아이디어) 규칙에 집중하라. 하나의 객체에 다중적 의미를 부여하지마라. 클래스 내에 클래스의 모든 사용자에게 관련되지 않은 행위가 많을 경우 그것의 이동을 고려해야 한다.


'실제 세계' 엔티티를 모델링하는 객체를 상상하기는 쉽다. 프로그램에서 사람, 장소, 사물을 다루고 있다면 그러한 사람, 장소, 사물을 표현하는 객체를 갖고 있는 모습을 생각할 것이다. 객체는 '컴퓨터 세계' 엔티티를 모델링할 수도 있다. 프로그램이 파일이나 창을 열어야 한다면 파일과 창을 표현하는 객체가 필요한 것은 분명하다. 분명하지 않은 건, 프로세스를 모델링하는 데 객체를 사용할 수 있다는 점이다.


프로세스에는 작업, 활동, 연산, 명령, 그 외 자신의 프로그램이 처리해야 하는 다른 비물리적 대상(things)들도 포함된다. 예를 들어, 전기장치 테스트 프로시저를 클래스에 모델링하길 원할 수도 있다. 그렇게 할 경우 장치 테스트를 시작할 때마다 클래스의 인스턴스를 생성하도록 해준다. 이러한 인스턴스들은 테스트의 순서화(sequencing)뿐만 아니라 그들이 표현한 각 테스트의 상태에 관해서도 알 것이다. 이와 유사하게 클래스를 이용해 은행계좌(bank account)를 여는 프로세스를 표현할 수도 있다. 해당 클래스의 인스턴스들은 계좌를 열기 위해 완료해야 하는 액션을 알아야 할 책임이 있고, 각 액션을 실행한 결과를 보관해야 할 것이다.


'물리적' 객체에 프로세스 관련 코드를 포함시키는 대신 프로세스를 객체로서 모델링할 경우 몇 가지 중요한 이점이 있다. 이는 상속을 이용하여 물리적 모델링 라인뿐 아니라 프로세스 라인을 따라 코드를 특수화 및 재사용할 수 있음을 의미한다. 또한 콜 스택(call-stack)에 내재적으로 표현되는 대신 객체에 명시적으로 저장되는 동시, 실행 프로세스의 각 상태와 함께 부분적 완료 상태에 여러 프로세스를 (실제 컴퓨터 프로세스가 아니라 모델링되는 프로세스) 가질 수도 있다.


자신의 프로그램에서 사용할 수 있는 객체의 특성을 몇 가지 생각하도록 도와준다. 시스템의 어떤 '계층'에 (자신의 아키텍처가 그러한 방식으로 구조화되었다면) 객체가 존재할 것인가? 오래 지속될 것인가 (예: 개인의 은행계좌), 임시적일 것인가 (예: 단일 거래)? 능동적인가 (예: 액션의 시퀀스를 구동시키는 test 객체), 수동적인가 (정보의 저장이나 검색 요청에만 응하는 test 로그)? 일반 목적인가 (데이터베이스로의 인터페이스), 특수 목적인가 (새로운 유형의 위젯)? 물리적 엔티티를 표현하는가 (예: 사람), 개념적 엔티티인가 (예: 신용 카드)? Private할 것인가 (시스템 특정 부분에서 당신만을 위해 작업을 수행), public할 것인가 (시스템에 전반적으로 사용)? 마지막으로, 무언가의 전체를 표현할 것인가 (예: 책 1권), 무언가의 일부만 표현할 것인가 (예: 한 페이지)?


마지막 요점도 중요하다. 가능한 한 잘게 나누어 구분된 객체를 이용해 조각(piece)을 표현하는 것은 좋은 생각이다. 그리고 다른 객체들을 이용해 이 조각들을 일관된 컬렉션으로 집합시킬 수도 있다. 이러한 방식으로 디자인하면 더 작고 간단한 클래스를 제공하여 재사용과 수정이 더 수월하다. 그리고 추후에 상속을 다루기도 수월해질 것이다. 예를 들어, 간단한 약속 리스트를 모델링하는 객체를 생성해야 한다면 두 개의 컬렉션을 포함하도록 만들 것을 고려해야 한다-하나는 약속을 설명하는 문자열을 포함한 컬렉션, 다른 하나는 약속 이름을 표현하는 정수(integer)를 포함한 컬렉션. 하지만 이보다는 하나의 약속에 대한 아이디어를 모델링하는 클래스 하나를 생성하는 편이 훨씬 낫다. 해당 클래스의 인스턴스들은 단일 문자열과 단일 정수를 보유할 것이다. 그러면 약속 리스트는 그러한 약속(appointment) 객체들의 컬렉션에 불과할 것이다.


객체들 간 관계

앞서 관찰한 바와 같이 객체들 간 관계는 여러 유형이 가능하다. 그 중 일부는 피어 투 피어(peer-to-peer)라 부르는 것이고, 나머지 관계들은 분명한 방향을 갖고 있다. 방향을 가진 관계들 중에는 인스턴스와 그 클래스 간 관계(스몰토크에서 이 관계는 객체들 간 관계나 다름없다), 클래스들 간 상속 관계가 있다 (이 또한 객체들 간 관계가 분명한데, 스몰토크에서는 다른 모든 것과 같이 클래스 또한 객체이기 때문이다).


이러한 특별한 유형의 관계는 무시하고, 자신의 디자인에서 생성할 수 있는 비상속적 관계 유형을 간략하게 살펴본 후 그러한 관계들이 스몰토크 구현부에서 어떻게 매핑되는지를 알아보자.


연관(associations)

자신의 디자인에서 객체들이 어떤 방식으로든 서로 연관되어 있기 때문에 서로에 대해 알고 있다는 상황을 생성할지도 모르겠다. 항상은 아니지만 보통 이것은 일대일 관계가 될 것이다. 이는 주로 다른 객체를 포함하게 될 한 객체 내에서 인스턴스 변수를 생성함으로써 구현된다. 아래 다이어그램이 해당 개념을 잘 설명해준다. 실제로 고려해야 할 점은 양방향으로 만들 것이냐(두 객체 모두 서로에 관해 아는 상황) 한방향으로 만들 것이냐가 된다 (한 객체만 나머지 객체에 관해 아는 상황). 이는 필요성을 비롯해 스타일의 문제다. 양방향 연관이 필요 없다면 당신 또는 당신의 코드를 재사용하게 될 사용자가 향후에 필요로 할 상황에 따라 자신이 결정해야 할 문제다. 양방향 연관을 만들면 두 개의 객체가 동시에 일관적으로 유지되도록 보장하기 위해 accessing 메서드를 이용해 두 객체 내 인스턴스 변수의 값을 설정하는 편리한 메서드를 명시하길 원할 것이다.


Ass image 12 01.png
객체들 간 연관관계는 (인스턴스 변수를 이용해 구현) 양방향 (customer/creditRecord) 또는 한방향이 (customer/address) 가능하다.


집합(aggregations)

집합은 부분들의 총합을 표현하는 객체가 있을 때 발생한다. 각각이 단일 인스턴스 변수에 의해 구현되는 객체들과의 연관을 다수 갖고 있는 객체가 이에 해당한다. 예를 들어, 자동차에는 엔진, 변속기, 본체 등이 있다. 다시 말하지만, 한방향이나 양방향 포인터 중에 어떤 것을 유지할 것인지는 자신이 결정해야 하지만, 그렇지 않을 경우 간단하다.


여기서 주의해야 할 유일한 한 가지는 이 관계를 표현하기 위해 상속을 이용하려는 속임수에 빠지지 않아야 한다는 점이다. '엔진은 자동차의 유형이 아니다'라고 계속 되새기길 바란다. 부분/전체 관계는 사실 자신의 프로그램에 또 다른 유형의 (상속에 더해) 계층구조를 가져오는데, 이는 당신이 구현을 책임져야 한다. 해당 계층구조는 여러 수준의 깊이로 되어 있고, 사실 그러한 계층구조는 하나 이상 존재할 수 있다.


집합은 다른 객체들이 컬렉션을 보유하는 객체가 될 수도 있다. 예를 들어, 한 권의 책은 페이지로 이루어진 컬렉션을 포함한다. 이러한 유형의 객체를 기존 컬렉션 클래스의 서브클래스로서 명시하는 것은 거의 대부분의 경우 올바르지 않다. 그 이유는 당신이 새로운 컬렉션 유형을 생성하는 것이 아니기 때문이다. 단지 다른 객체들의 컬렉션을 포함하는 새로운 객체 유형을 생성할 뿐이다. 이러한 경우, 재사용 방법으로 컬렉션을 서브클래싱함으로써 기능을 상속하는 대신, 컬렉션을 포함하는 객체를 디자인함으로써 기능을 캡슐화하는 방법이 선호된다.


물론 컬렉션 내 객체들이 ('부분들이') 컬렉션을 보유하는 객체로의 ('집합') 참조를 유지해야 할 것인지는 여전히 본인의 결정에 달려 있다. 다시 말하자면, 페이지는 자신들이 어떤 책에 포함되어 있는지를 알고 있는지를 결정하는 것이다. 컬렉션 내 부분들을 조작하기 (추가, 제거 등) 위해 집합 내에 편리한 메서드를 제공할 것인지 여부 또한 본인이 결정해야 한다. 대안적으로, 전체 컬렉션을 리턴하는 집합에 accessing 메서드를 제공하여 다른 객체로의 완전한 접근성을 부여하는 방법이 있다. 당신이 집합을 컬렉션으로 구현하고 있다는 사실이 실제로 얼마나 private할 것인지에 대해서도 결정을 내려야 할 것이다.


의존성

의존성이야말로 특별한 연관 사례에 해당한다. 의존성은 한 객체가 다른 객체에 일어나는 변화에 관심이 있을 때 발생한다. 스몰토크는 이러한 유형의 관계를 특별히 처리하기 위해 '의존성 메커니즘'이라는 특수 메커니즘을 제공한다. 이는 MVC의 기반이 되므로, 자신의 디자인이 사용자 인터페이스 요소들을 통합할 경우 의존성 관계를 어디에 생성할 것인지 주의 깊게 고려해야 할 것이다. 하지만 원한다면 다른 (비 UI) 객체들 간 의존성을 생성하는 것도 가능하다. 강력한 기능이지만 의존성 관계는 명시적 연관보다 디버깅이 더 까다롭기 때문에 이를 명시할 때는 주의를 기울여야 한다. 이유는 두 객체가 의존성을 통해 통신할 경우 단순히 덜 분명하기 때문이다.


재사용 디자인하기

OOP가 보장하는 기능 중 하나는 기존 애플리케이션으로부터 코드를 재사용할 수 있는 가능성이다. 불행히도 다른 곳에서와 마찬가지로 이러한 재사용은 공짜로 가능하거나 자동적으로 이루어지지 않는다. 클래스가 재사용 가능해야 한다면 재사용되도록 디자인해야만 한다. 재사용을 위한 디자인을 살펴보기 전에 '재사용 가능성'의 의미를 먼저 고려해보자.


재사용 가능성이란 무엇인가?

클래스는 다른 프로그래머들이 자신의 클래스를 작성하는 것보다 기존의 클래스를 사용하고자 할 때에만 재사용이 가능하다. 즉, 재사용 가능한 클래스는 애플리케이션의 기술적 요구를 충족시켜야 할 뿐만 아니라 사용도 쉽고 이해도 가능해야 한다는 말이다. 기술적 요구의 충족이란, 필요한 작업을 신뢰성 있게, 효율적으로, 불필요한 부가적 효과 없이 실행함을 의미한다. 클래스를 사용이 쉽고 이해 가능하게 만든다는 것은 클래스가 하는 일을 예측 가능하도록 보장하고, 그에 따라 일반적으로 스몰토크 '스타일'을 준수하도록 보장한다는 의미인데, 본 내용은 다음 장에서 논할 것이다.


모든 클래스가 재사용 가능해야 한다는 의미는 아니라는 사실을 기억하는 것이 중요하다. 재사용이 가능하려면 시간과 노력이 필요하고, 그러한 자원은 좀 더 적절한 클래스를 재사용 가능하도록 만드는 데 소비해야 할 것이다. 재사용 가능성이 OOP에서 자동으로 이루어지지 않는 것처럼 모든 객체가 재사용 가능하게 작성되어야 한다고 정해진 것도 아니다. 하지만 클래스를 재사용 가능하도록 만들기로 결정했다면 어떠한 방식으로 재사용될 것인지도 결정해야 한다.


재사용 가능성의 유형

재사용과 관련해 클래스에는 두 가지 인터페이스가 있다. 재사용자는 클래스를 캡슐화하거나 (클래스의 인스턴스를 자신의 클래스의 인스턴스 내부에 위치시킴), 그로부터 상속할 수도 있다. 이 두 가지 메커니즘은 두 가지 재사용 모델을 반영하는데, 두 가지 모두 스몰토크에서 이용 가능하다. 클래스 라이브러리는 서로 플러그가 가능한 부분들의 컬렉션으로 생각할 수 있고, 아니면 클래스 라이브러리를 다양한 시점에서 상속 가능한 트리로 생각할 수 있겠다. 스몰토크에서는 부분들(parts)이 서브클래스보다 더 재사용 가능한 것이 보통이다.


스몰토크에서 프로그래밍을 한 경험이 있다면 클래스를 상속하는 경우보다 기존 클래스의 인스턴스를 만드는 경우가 훨씬 잦다는 사실을 깨달을 것이다. 숫자, 문자열, 컬렉션, 위젯 등의 인스턴스를 항상 만든다. 이러한 클래스 중 어떠한 것에서든 상속하는 경우가 매우 드물며, 현재까지 가장 흔한 슈퍼클래스가 object다. 자신의 재사용 가능한 클래스를 생성할 때는 다른 사람이 그 클래스로부터 어떻게 상속할 것인지 보다는 그 클래스의 인스턴스들을 어떻게 사용할 것인지를 더 깊이 생각해야 한다는 의미다. 당신이 상속인(inheritor)이 되어야 하는 경우라도 상속을 위한 디자인은 충분히 힘들다. 더 나아가 미래에 알려지지 않은 상속을 디자인하기란 일반적인 사용이라기엔 너무 어렵다.


플러그인 가능하게 디자인된 부분들을 빌드하면 (이러한 플러깅을 기술적 용어로 구성composition 이라 함) 스몰토크가 사용하는 (다수의 부모 클래스 조합 대신 정확히 하나의 슈퍼클래스를 갖는다) 단일 상속 시스템의 한계를 지나치도록 해준다. Composite 객체의 각 부분은 고유의 상속 계층구조에서 자신만의 장소를 가질 수 있다. 클래스 계층구조에서 단일 일체적(monolithic)에 상응하는 위치는 항시 타협을 통해야 한다.


클래스의 재사용 가능한 범위도 결정해야 한다. 애플리케이션 내 한 군데 이상의 곳에서 사용할 클래스를 생성했다면 사실상 재사용 가능한 클래스를 가진 것이다. 하지만 더 나아가 동일한 애플리케이션을 작업하는 팀 구성원들이 재사용하도록 만들길 원하는 경우도 있다. 추후 애플리케이션에서 당신이나 팀에 속한 다른 사람들에 의해 재사용될 수 있는 클래스를 원할 수도 있다 (매우 흔한 재사용 형태는 이전 프로젝트를 위해 작성한 클래스를 현재 프로젝트에 사용하는 형태이다-해당 클래스에 대한 학습 곡선이 없을 것이고, 짐작컨대 당신이 신뢰할 것이며, 그에 따라 재사용하고자 하는 의욕이 있다.). 마지막으로, 이전에 만난 경험이 없는, 심지어 클래스를 구매할지도 모르는 타인에 의해 재사용 가능한 클래스를 생성할 수도 있다. 클래스의 재사용 가능 범위는 재사용을 위한 아래 고찰사항에 얼마만큼 노력을 기울이는지에 영향을 미칠 것이다.


재사용 가능한 코드 작성하기

가장 분명한 일은 코드를 포괄적으로 만들기 위해 노력하는 것이다. 스몰토크에서 이를 성취하기 위한 핵심은 바로 '타입 없음'이 된다. 인터페이스부에서 객체의 타입을 클래스로 제한할 필요가 없다면 굳이 하지마라. 만일 제한할 경우, 필요한 만큼만 제한하라. 예를 들어, 메서드가 컬렉션을 매개변수로서 수용할 경우, 자신의 코드를 가령 배열에 그치지 않고 가능한 한 많은 종류의 컬렉션과 작동하도록 만들어라.


클래스를 좀 더 일반적으로 만드는 방법은, 상수를 메서드로 하드코딩하는 것을 피하는 방법이다. 상수를 메서드에 대한 매개변수로서 인터페이스부로 가져오는 것이 옳다면 그렇게 하라. 물론 그렇게 할 경우 클래스에 대한 인터페이스부를 복잡하게 만들 수 있으므로, 다양한 메서드의 (convenience 메서드라 불림) 간단한 버전을 생성하는 방법이 일반적이다. 이러한 메서드들은 복합 메서드보다 적은 매개변수를 취하지만 재구현하지 않는다. 단순히 매개변수에 대한 기본 값으로 된 복합 메서드를 더 많이 호출할 뿐이다. 이는 실제로 일을 하는 메서드가 호출되기 전에 일반성이 증가하는 수준(level)을 (그리고 증가하는 매개변수의 수를) 많이 통과하는 클래스 라이브러리에서 자주 발생함을 볼 수 있다.


아마도 위와 상충되는 것은 (그렇지만 그때야말로 당신의 판단력이 발휘되는 곳이다) 클래스의 구현부를 가능한 한 많이 캡슐화하고 싶은 욕구가 될 것이다. 다시 말하자면, 클래스가 작용하는 방식에 내재적이거나 클래스가 제공하는 서비스를 수정하거나 구성하기 위해 굳이 접근할 필요가 없는 인터페이스 기능을 통해 노출하지 말라.


사용자가 사실상 사용하는 기능만 알면 되는 클래스를 디자인하도록 하라. Convenience 메서드를 제공하는 것이 좋은 방편이 되겠다. 또한 자신의 클래스를 제 1부에서 살펴본 어댑터 방식으로 '결합 가능'하고 '플러그인 가능'하게 만드는 것을 목표로 삼도록 한다.


일반적으로 당신의 클래스가 미래에 어떠한 용도로 사용될 것인지에 대해 생각해보라. 절대로 미래의 모든 용도를 예측할 수는 없으며, 당신의 클래스가 최고 수준의 재사용 요구조건을 갖지 않는 한 시간을 그러한 용도를 예측하는 데 시간을 낭비하지 말라. 하지만 클래스의 직접적 목적을 성취할 방도를 찾는 동시 그 기능을 일반적인 방식으로 디자인할 수 있다면 그쪽을 택하는 것이 좋겠다.


상속 이용하기

'객체를 찾고' 나서 객체 지향 프로그래밍 초보자들이 가장 어려움을 겪는 일은 아마 상속의 이용과 관련될 것이다. 사실 숙련된 프로그래머들조차 상속의 이용과 관련해 곤경에 빠지곤 하는데, 다행히 이 또한 시간이 지나면서 수월해진다. 한 가지 기억해야 할 중요한 사항은, 상속은 무언가를 어렵게 만드는 것이 아니라 쉽게 만드는 것을 목적으로 만들어진 기능이다. 엄청나게 깊고 효율적인 상속 계층구조를 생성하기가 단순히 불가능하다면 고군분투하지마라. 하지만 상속을 이용하는 방법에 관해 몇 가지 단순한 사항을 (그리고 사용 방법에 대해) 알고 있다면 자신의 코드를 더욱 더 멋들어지게 만들 수 있음을 발견할 것이다.


여기서는 스몰토크에서 상속이 어떻게 작용하는지 독자가 이해하고 있다고 가정할 것이다. 스몰토크 시스템에 의해 구현되는 방식을 이해해야 한다는 의미가 아니라 메서드와 변수는 서브클래싱을 통해 슈퍼클래스로부터 상속되고, 상속된 메서드를 오버라이드할 수 있음을 이해해야 한다는 말이다. 스몰토크에서 상속의 의미가 불확실하다면 제 2장, 객체 입문을 다시 살펴보라.


너무 일찍 염려하지 마라

상속의 첫 번째 규칙은 나중을 위해 남겨두는 것이다. 너무 일찍 상속을 고려하지 마라. 적어도 자신의 디자인 내 다양한 클래스들의 구현부가 어떻게 유사한지 (혹은 어떻게 재디자인할 수 있을지) 고려하기 이전에 클래스에 관해 알아야 한다. 때로는 돌아가서 상속을 사용하기 전에 작동하는 시스템을 코딩해야 할 것이다. 때로는 클래스들이 실제로 하는 일은 해당 기능성의 일부를 슈퍼클래스로 뽑아낼 수 있다는 완전한 일반성을 인식하기까지 당신이 빌드한 클래스를 이용한 실제적 경험이 필요할 수도 있다. 이를 실행 시 상속 계층구조를 생성하는 과정은 하향식, 상향식, 심지어 중앙식(middle-out) 활동도 가능함을 인식하라.


코드의 재사용에 상속을 이용하라

두 번째 규칙은 코드의 재사용에만 상속을 사용하는 방식이다. 서브클래스와 그 슈퍼클래스의 상속 관계를 '~의 종류'로 읽을 수 있음을 기억하라. 하지만 '~의 종류'라는 것은 다양하게 해석이 가능하다. 가령, 인간이 말하는 '~의 종류'와 컴퓨터가 말하는 '~의 종류'가 있는데, 어떤 종류가 실제 세계인지, 스몰토크에서 모델링에 사용된 객체는 어떤 종류인지 구별해야 한다. 따라서 송어를 모델링하는 데 몇 가지 추가(additions)와 함께 ApplicationModel 이 적당하다면, 인간이 말하는 송어는 물고기의 종류지만 스몰토크에서는 ApplicationModel 의 종류가 된다.


상속 계층구조를 모델링할 때 스몰토크 객체에서 코드 최적화를 위해 사용하는 대신, 모델링하고자 하는 실제 객체에 대한 분류 체제로서 사용하고픈 함정에 빠지기가 매우 쉽다. 이것이 바로 서브타이핑과 서브클래싱의 차이다. 시스템 클래스 라이브러리에서 상속의 올바른 사용을 훌륭하게 보여주는 예제는 클래스 프로세스로, 사실 Link 클래스의 서브클래스에 해당한다. 실세계에서 프로세스는 액션 과정의 종류지만, 스몰토크에서는 프로세스의 주요 기능들은 Link와 공통된 내용을 많이 공유하므로 Link의 특별한 종류로서 모델링된다.


개념적 계층구조와 (실제 세계) 구현 계층구조의 (스몰토크 세계) 차이를 보여주는 또 다른 좋은 예로 원과 타원의 차이를 들 수 있겠다. 실제 세계에서 원은 타원의 특별한 종류로, 장축과 단축의 길이가 같다. 따라서 원은 타원의 서브타입이다. 하지만 스몰토크 세계에선 아마도 circle을 단일 diameter 변수를 가진 슈퍼클래스로 만들고, 그것을 서브클래싱하여 추가 변수를 추가하는 Ellipse를 얻을 것이다.


한 가지 상기할 점이 있는데, 계층구조의 일부를 생성하기 위해 상속을 사용하는 일을 피해야 한다는 점이다. 어리석게 들릴 수도 있지만 적어도 상속을 사용하는 데 익숙하기 전까지는 이렇게 생각하기 매우 쉽다. 문(door)은 집의 일부긴 하지만 집의 종류는 아니다. 앞에서 살펴본 연관(association)을 생각해보자. 상속은 당신이 마음대로 사용할 수 있는 객체들 간에 존재하는 많은 유형의 관계 중 하나에 불과하다는 사실을 기억하라.


행위(behaviour)의 특수화에 상속을 이용하라

생각해보면 스몰토크의 상속 메커니즘을 슈퍼클래스로부터 행위를 삭제, 수정 또는 추가하기 위한 서브클래스를 얻는 데 사용할 수도 있음을 알 수 있을 것이다 (다음 페이지 다이어그램 참고). 행위의 추가는 단순히 더 많은 메서드를 정의하거나, 특정 메서드를 재정의(오버라이딩)한 후 super로의 호출에 넣음으로써 가능하다. 행위의 수정은 다른 일을 행하는 메서드를 오버라이드하여, 상속된 본래 기능을 교체함으로써 가능하다. 스몰토크에서 행위의 삭제는 self shouldNotImplement 표현식을 포함하는 메서드로 기존 메서드를 오버라이드하여 가능하다. 누군가 당신의 코드로 해당 메시지의 전송을 시도할 경우 예외를 발생시킬 것이다.


스몰토크에서 상속은 거의 대부분 가산적(additive)이다. 즉, 슈퍼클래스의 기능 위에 서브클래스의 기능을 증가시키기 위해 서브클래스에 추가 메서드가 놓이거나, 행위를 수정하거나 향상시키기 위해 메서드가 오버라이드된다는 의미다. 감산적(subtractive) 상속은 사용되는 경우가 드물다. 즉, 서브클래스는 그들의 슈퍼클래스의 구체적인 버전인 경향이 있다. 그 이유는, 어떤 개념을 모델링하는 클래스를 취해 서브클래싱을 통해 좀 더 일반적인 개념을 모델링하도록 만들기란 뭔가 이상하기 때문이다. 상속 계층구조를 디자인할 때는 이러한 점을 주의하라. 상단에서 가장 일반적인 모델로 시작하여 하향으로는 좀 더 구체적으로 진행하라.


Ass image 12 02.png
상속을 이용해 기능을 남겨두거나 (메서드 A), 수정하거나 (B), 교체하거나 (C), 제거하거나 (D), 추가할 (E) 수 있다.


서브클래싱은 클래스의 인스턴스가 표현할 수 있는 것들을 제한한다는 의미로 생각할 수도 있겠다. 예를 들어, 스몰토크에서 Collection 클래스는 어떤 유형의 객체 컬렉션이든 표현한다. 하지만 그 서브클래스들 중 하나인 SequenceableCollection은 객체의 컬렉션을 좀 더 잘 정의된 순서로 모델링할 수 있는 것들을 이미 제한하고 있다. 그 클래스의 서브클래스는 (ArrayCollection) 외부 정수 키에 의해 정의된 정렬로 제한하므로, 클래스 계층구조 하향으로 지속된다.


이러한 규칙에서 유일하게 예외가 발생하는 때는 시스템 클래스가 실제보다 더 일반적인 일을 실행하도록 만들 때이다. 시스템을 '슈퍼클래싱'하기보다는 서브클래싱하는 것이 훨씬 수월하다. 기존에 있는 계층구조 중간에 클래스의 삽입을 시도할 수는 있지만 이를 통해 낮은 계층에 있는 행위를 변경하지 않는 편이 좋을 것이다. 따라서 구체적인 구현부를 취해 서브클래스에서 좀 더 일반적이도록 만드는 작업이 남는다.


행위의 공유에 상속을 이용하라

책의 앞부분에서 추상 클래스를 살펴본 바 있다. 추상 클래스는 인스턴스가 만들어진 적이 없는 클래스다. 추상 슈퍼클래스를 이용해 여러 구체적 서브클래스들에 의해 공유되지만 자체로는 완전할 수 없는 기능을 수집할 수 있다. 이는 기능을 공유하는 두 개의 클래스를 생성하고자 할 때 발생하는 문제를 해결하지만 두 클래스 모두 서로의 자연적인 슈퍼클래스가 아님을 발견할 것이다. 아래 다이어그램과 같이 공유된 기능을 단순히 세 번째 클래스로 '추출'하여 두 클래스를 세 번째 클래스의 구체적 서브클래스로 만들어라. 이는 두 클래스를 자매(sibling) 클래스로 만들어, 슈퍼클래스로부터 공통된 기능을 공유하도록 해준다. 이렇게 구성된 디자인은 사실상 클래스를 다른 클래스의 서브클래스로 강요할 때보다 (논리적이지 않음) 훨씬 더 세련되다.


Ass image 12 03.png
상속을 이용해 공통 메서드를 추상 슈퍼클래스에서 정의하고 유일한 메서드를 구체적 서브클래스에서 정의함으로써 기능을 공유할 수 있다.


하지만 상속을 통해 기능을 부적절하게 공유하지 않도록 주의하라. 클래스가 그 목적에 부합하지 않는 메서드 전체를 단순히 상속할 경우, 그 슈퍼클래스에서 제거한 후 자매 클래스로 이동해야 할 것이다. 이것이 아니라면 전체 계층구조를 재구성할 필요가 있다. 추상적 슈퍼클래스 계층구조의 구조화 방법을 보여주는 예는 Collection 클래스와 그 서브클래스 일부를 살펴보라 (클래스 주석은 보통 클래스가 추상적인지 구체적인지 알려준다).


행위의 인식에 상속을 이용하라

추상 슈퍼클래스를 생성하는 또 다른 이유는, 이후 하나 또는 그 이상의 서브클래스에서 구현되는 인터페이스의 명세(specification)를 제공하기 위함이다. 인터페이스부를 구현부와 분리하는 이유는 훌륭한 OOD의 목표 중 하나임을 기억하라.


스몰토크에서는 메서드를 명시할 수 있지만 올바른 이름과 매개변수를 가진 메서드를 작성한 후 단순히 self subclassResponsibility 표현식을 이용해 구현함으로써 슈퍼클래스 내 구현되지 않은 채 남겨둘 수 있다. 메서드는 이후 아래 다이어그램에서 보듯이 슈퍼클래스 안에서 적절한 구현부로 오버라이드된다. 이러한 유형의 구성은 만일 그 메서드를 호출하는 메시지가 슈퍼클래스의 인스턴스 혹은 메서드의 오버라이드를 실패한 서브클래스의 인스턴스로 전송될 경우 예외를 발생시킬 것이다. 메서드의 예외 둘 다 메서드를 재구현할 필요가 있는 서브클래서(subclasser)로 보내는 신호이다.


Ass image 12 04.png
상속을 이용해, 추상 슈퍼클래스에서 메서드를 선언 후 구체적 서브클래스에서 정의함으로써 기능을 인식할 수 있다.


메서드에 대한 인터페이스부를 슈퍼클래스에서 정의하여 서브클래스에서 구현할 때 당신은 여전히 다른 슈퍼클래스 코드에 not-yet-implemented(아직 구현되지 않은) 메서드를 호출할 수 있다. 예를 들어, Magnitude 클래스에는 < 메서드가 정의되어 있지만 구현되어 있지 않다. 하지만 이후 Magnitude에서 > 메서드의 구현에 사용된다. 이는 Magnitude의 구체적 서브클래스는 고유의 <를 구현할 것이기 때문에 괜찮으며, 상속된 >가 그 메서드를 호출하고 그에 따라 적절하게 작동할 것임을 의미한다. 분명하지 않다면 이 영역에서 시스템 클래스 계층구조를 살펴보고, 그 외 예는 런처에서 browse->senders를 이용하면 subclassResponsibility를 사용하는 모든 메서드와 클래스를 볼 수 있을 것이다.


이번 장에 걸쳐서는 스몰토크에 적용되는 객체 지향 디자인의 중요한 원칙을 다수 살펴보았다. 먼저 디자인 고찰사항을 먼저 살펴보았다. 중요한 디자인 고찰사항은 다음과 같았다:

  • 인터페이스부터 구현부와 별개로 간주하라.
  • 복잡성을 숨겨라.
  • 클래스들 간 의존성을 최소화하라.
  • 사용자 인터페이스를 애플리케이션 로직과 분리된 채 유지하라.
  • 복잡한 알고리즘을 제거하라.
  • 복합 변수를 제거하라.
  • 가능한 한 특수 목적의 클래스는 적게 생성하라.
  • 클래스 로드맵을 염두에 두어라.
  • 간단하게 유지하라


개발자들이 스몰토크 프로그램을 디자인할 때 일반적으로 행하는 작업 또한 살펴보았다. 디자인은 반복적이고 기회주의적인 활동이므로 이러한 작업들은 재정렬되어도 좋고, 그 중 일부는 생략 가능하며, 또 일부는 당신이 디자인하는 것이 무엇이든 그에 대한 이해가 향상되면서 반복해도 좋다. 우리가 살펴본 작업은 다음과 같다:

  • 필요한 기능 결정하기.
  • 어떤 객체가 기능을 제공할 것인지 확인하기.
  • 객체를 클래스로 그룹화하기.
  • 객체들이 서로 어떻게 연관될 것인지 결정하기.
  • 클래스에 대한 인터페이스부 디자인하기.
  • 상속을 이용해 구현부 그룹화하기.


마지막으로 우리는 객체 확인하기, 객체들 간 관계 확인하기 (연관, 집합, 의존성), 양호한 재사용성을 위한 디자인하기를 비롯해 상속을 효과적으로 사용하는 방법을 마지막으로 살펴보았다.


이것이 전부다. 애플리케이션을 위한 '객체 찾기'를 도와줄 마법을 기대하면서 본장을 읽은 사람은 아마 실망을 감추지 못할 것이다. 하지만 우리가 논한 지침의 일부만 유념하더라도, 앞서 고려한 상황에 마주쳤을 때 다시 본문을 참조할 것을 기억하기만 한다면 스몰토크 시스템의 디자인은 더 이상 까다로운 작업이 아니라 스몰토크에서 자연스러운 코딩의 전조가 된다는 사실을 발견할 것이다. 따라서 다음 장의 주제는 스몰토크에서 코딩의 예술(미)이 되겠다.


부록: 디자인 방법론

객체 지향 프로젝트를 위한 Hewlett Packard의 Fusion 방법론은 HP Labs Bristol에서 발전되었다. 이는 아래 자료에 완전히 설명되어 있다:

Object Oriented Development: The Fusion Method
DerekColeman etal., Prentice-Hall 1994
ISBN 0-13-338823-9


VisualWorks 프로젝트에 관한 ParcPlace의 Object Behavior Analysis and Design (OBA/D)는 아래 자료에 설명되어 있다:

Succeeding with Objects
Adele Goldberg and Kenneth Rubin, Addison-Wesley 1995


Notes