추상화 기법
•
추상화는 누누히 말해왔듯 도메인의 복잡성을 단순화하고 직관적인 멘탈 모델을 만드는 데 사용하는 가장 기본적인 인지 수단이다.
◦
도메인에 존재하는 개념들을 구조화, 단순화하기 위해 다양한 추상화 기법을 사용할 수 있다.
◦
특성을 공유하는 객체들을 동일한 타입으로 두는 것은 객체지향에서 사용하는 추상화 기법의 한 예다.
•
다음은 사람들이 세계를 이해하는 데 사용하는 추상화 기법의 종류로, 각 추상화 기법은 복잡성을 낮추기 위해 사물의 특정한 측면을 감추는 공통점이 있다.
◦
분류와 인스턴스화
▪
분류는 객체의 구체적인 세부 사항은 숨기고 인스턴스들이 공유하는 공통적인 특성을 기반으로 범주를 형성하는 과정이다.
▪
분류의 역은 범주로부터 객체를 생성하는 인스턴스화 과정이다.
◦
일반화와 특수화
▪
일반화는 범주 사이의 차이를 숨기고 범주 간에 공유하는 공통적인 특성을 강조한다.
▪
일반화의 역을 특수화라고 한다.
◦
집합과 분해
▪
집합은 부분과 관련된 세부 사항은 숨기고 부분을 사용해 전체를 형성하는 과정을 가리킨다.
▪
집합의 반대 과정은 전체를 부분으로 분리하는 분해 과정이다.
•
객체지향의 가장 큰 장점은 동일한 추상화 기법을 프로그램의 생애주기 전반에 걸쳐 일관성있게 적용할 수 있다는 점이다.
⇒ 각 단계마다 다른 기법을 사용하게 되면 복잡도가 올라갈 것 같긴 하다. 각 단계의 추상화 기법의 결과를 이어주는 일종의 인터페이스도 필요할 것 같고
분류와 인스턴스화
개념과 범주
•
객체를 분류하고 범주로 묶는 것은 객체들의 특정 집합에 공통의 개념을 적용하는 것을 의미한다.
◦
승용차, 버스, 트럭 등을 자동차로 묶을 수 있는 이유는 색상, 크기, 외관 등이 완전히 동일하지 않더라도 공통점을 바탕으로 자동차라는 하나의 개념으로 묶을 수 있기 때문이다.
•
개념이란 속서오가 행위가 유사한 객체에 공통적으로 적용되는 관념이나 아이디어다.
◦
자동차 범주에 적용되는 개념은 바퀴를 이용해 사람들을 한 장소에서 다른 장소로 운반하는 운송수단이다.
•
객체들을 공통적인 특성을 기반으로 범주로 묶고 개념을 적용하는 것은 범주라는 정신적인 렌즈를 통해 바라보는 것과 유사하다.
◦
자동차라는 렌즈를 통해 바라본 세상에는 자동차 범주에 속한 객체만이 보일 것이고 나무라는 렌즈를 통해 바라본 세상에는 나무 범주에 속한 객체만이 보일 것이다.
•
세상에 존재하는 객체에 개념을 적용하는 과정을 분류라고 한다.
◦
분류는 객체를 특정한 개념을 나타내는 집합의 구성 요소로 포함시킨다.
◦
어떤 객체를 자동차라는 개념으로 분류하는 것은 자동차라는 개념을 적용시킬 수 있는 집합의 일원으로 해당 객체를 포함시키는 것이다.
•
세상에 존재하는 서로 다른 상태의 자동차와 나무를 개별적으로 다루면 너무 복잡하므로 이를 자동차나 나무라는 범주로 묶음으로써 복잡성을 제어한다.
◦
사람들은 분류를 통해 개별 현상들을 하나의 개념으로 다룬다. 이때 수많은 개별적인 현상들을 객체라고 하고 하나의 개념을 타입이라고 한다.
◦
다시 말해 분류는 객체를 타입과 연관시키는 것이다. 분류의 역은 타입에 해당하는 객체를 생성하는 과정으로 인스턴스화 또는 예시라고 한다.
•
객체지향의 세계에서 개념을 가리키는 표준 용어는 타입이다. 따라서 타입은 개념과 동의어다.
⇒ 타입을 설명하라고 한다면 앞서 말한 개념의 정의를 차용하면 될 것 같다.
이런 관점에서 분류란 객체들을 동일한 타입 또는 범주로 묶는 과정을 의미하므로 객체를 타입의 인스턴스라고 한다.
타입
•
객체를 타입에 따라 분류하기 위해서는 객체가 타입에 속하는지 여부를 확인할 수 있어야 한다.
•
어떤 객체를 자동차라는 개념에 포함시키려면 자동차라는 개념이 가지는 명확한 정의가 필요하다.
•
타입을 객체의 분류 장치로서 적용할 수 있으려면 다음과 같은 세 가지 관점에서의 정의가 필요하다.
◦
심볼
▪
타입을 가리키는 간략한 이름이나 명칭
▪
자동차 타입에서는 자동차라는 명칭이 해당한다.
◦
내연
▪
타입의 완전한 정의, 내연의 의미를 이용해 객체가 타입에 속하는지 아닌지 확인할 수 있다.
▪
자동차 타입에서는 원동기를 동력원으로 해서 주행하는 사람이나 화물을 운반하는 기계가 해당한다.
◦
외연
▪
타입에 속하는 모든 객체들의 집합
▪
외연은 버스, 트럭 등 모든 자동차의 집합이다.
외연과 집합
•
타입의 외연은 앞서 타입에 속하는 객체들의 집합으로 표현한다고 했다. 집합은 외연을 가리키는 또 다른 명칭으로 객체들은 동시에 서로 다른 집합에 포함될 수도 있다.
•
컴퓨터는 데스크톱, 노트북, 사무용이라는 세 가지 범주로 분류할 수 있다.
◦
각 집합은 데스크톱, 노트북, 사무용이라는 타입의 내연의 정의를 만족하는 객체를 원소로 포함한다.
◦
이때, 노트북을 사무용 컴퓨터로 사용하면? 해당 객체의 타입은 노트북이면서도 사무용인 것이다.
◦
즉, 동시에 한 개 이상의 집합에 포함되는 것이다.
◦
사람들은 한 시점에 동일한 객체를 다양한 방식으로 인지하기 때문에 이것은 사람들이 세상을 인지하는 일반적인 방식을 반영한다.
•
이처럼 객체가 하나 이상의 타입에 동시에 속할 수 있기 때문에 단일 분류와 다중 분류라는 개념이 존재한다.
◦
단일 분류란 한 객체가 한 시점에 하나의 타입에만 속하는 것을 의미한다.
◦
다중 분류란 한 객체가 한 시점에 하나 이상의 타입에 속하는 것을 의미한다.
•
단일 분류에 따르면 어떤 컴퓨터는 하나의 집합에만 속할 수 있다. 다중 분류는 하나 이상의 집합에 속할 수 있다.
•
대부분의 객체지향 프로그래밍 언어는 단일 분류만을 지원하는데 이는 한 객체는 오직 한 클래스의 인스턴스여야만 하며 동시에 두 개의 클래스의 인스턴스일 수 없다는 의미다.
◦
이 관점에서 다중 분류를 다중 상속과 혼동해서는 안 된다.
▪
다중 상속은 하나의 타입이 다수의 슈퍼 타입을 가질 수 있도록 허용하지만 타입 정의를 생략하지는 않는다.
▪
다중 분류는 특정한 타입을 정의하지 않고도 하나의 객체가 서로 다른 타입의 인스턴스가 되도록 허용하는 것이다.
•
만약 객체가 타입을 변경할 수 있다면 어떻게 될까?
◦
객체가 한 집합에서 다른 집합의 원소로 자신이 속하는 타입을 변경할 수 있는 경우를 동적 분류라고 한다.
◦
객체가 자신의 타입을 변경할 수 없는 경우를 정적 분류라고 한다.
•
다중 분류와 동적 분류가 서로 배타적인 관계에 있지 않으므로 이 둘을 동시에 적용할 수 있으며 이는 실세계의 복잡성을 모델링하는 데 매우 유용하다.
•
그러나 대부분의 언어는 클래스로부터 인스턴스를 일단 생성하게 되면 변경할 수 있는 방법을 제공하지 않으므로 타입을 변경할 수 없다.
◦
따라서 우리가 사용하는 언어의 대부분은 정적 분류를 허용하며 동적 분류를 구현할 수 있는 방법을 제공하지는 않는다.
•
이는 앞서 말했듯이 실세계의 복잡성을 다중 분류와 동적 분류를 이용해 모델링하더라도 언어의 제약으로 인해 이를 구현으로 옮기기는 쉽지 않다.
◦
저자의 개인적인 경험에 따르면 다중 분류와 동적 분류 관점에서 도메인 모델의 초안을 만들고 실제 구현에 적합하도록 단일 분류와 정적 분류 방식으로 객체들의 범주를 재조정하는 편이 분석과 구현 간의 차이를 메울 수 있는 가장 현실적인 방법이라고 제안한다.
•
마틴 파울러는 자신의 저서에서 동적 분류와 다중 분류를 구현할 수 있는 다양한 방식의 디자인 템플릿을 소개하고 있다.
◦
이러한 디자인 템플릿은 유연성이라는 측면에서 반드시 필요한 경우에만 사용해야 한다. 단순함을 위해서는 항상 다중 분류와 동적 분류보다는 단일 분류와 정적 분류를 선택하는 것이 현명하다.
클래스
•
클래스는 객체지향 프로그래밍 언어를 사용해 타입을 구현하는 가장 보편적인 방법이다.
◦
여기서 저자가 타입을 구현한다고 표현하는 이유는 클래스와 타입이 완전히 동일한 개념이 아니기 때문이다.
◦
클래스는 타입을 구현하는 용도 외에도 코드를 재사용하는 용도로 사용되기도 한다. 클래스뿐만이 아니라 추상 클래스, 인터페이스를 사용해서 타입을 구현할 수도 있기 때문이다.
•
역사적으로 분류에 대해 인정받을만한 최초의 연구는 아리스토텔레스의 분류법으로 이는 모든 자연계의 사물에 대한 완전하고 상세한 분류체계를 제공하는 것이었다.
•
현재의 객체지향 패러다임은 이런 아리스토텔레스의 분류법의 근간을형성하는 아이디어를 기반으로 한다.
◦
만약 객체들이 동일한 특성을 가진다면 그들은 동일한 카테고리에 속한다. 따라서 객체들의 카테고리는 객체들이 공유하는 공통적인 특성에 의해 정의된다.
◦
아리스토텔레스는 객체의 특성을 본질적인 속성과 우연적인 속성으로 분류했는데, 본질이란 한 사물의 가장 핵심적이고 필수불가결한 속성이며 그렇지 않은 속성을 우연적인 속성이라고 한다.
▪
어떤 사람이 취직을 해서 회사원이 되었다고 해도 그 사람은 그 사람일 뿐이다. 회사원이라는 역할이 그의 본질을 바꾸지는 못한다.
•
클래스 기반의 객체지향 언어는 아리스토텔레스의 철학을 기반으로 한다. 즉 클래스는 객체가 공유하는 본질적인 속성을 정의한다.
•
자바스크립트처럼 클래스가 존재하지 않는 프로토타입 기반의 언어의 경우, 이런 아리스토텔레스의 객관적인 분류 체계가 존재한다는 사상에 대한 철학적 의문에서부터 출발한다.
◦
클래스가 없는 프로토타입 언어에서 분류와 인스턴스화는 프로토타입이라는 객체의 복사를 통해 이뤄진다.
일반화와 특수화
범주의 계층
•
카를로스 린네가 발표한 자연의 체계라는 논문에서는 계라는 최상위 단계가 있는 중첩된 계층 구조의 분류 체계가 등장했다.
•
또한 해당 분류 체계의 또 다른 특징은 이명법을 적용했다는 것이다. 이명법은 언어에 따라 다르게 불리는 생물에 대해 표준 명칭을 정의하기 위해 모든 생물에 대해 속명과 종명을 혼합한 이름을 붙이는 것을 의미한다.
•
린네의 분류 체계는 범주 간의 계층적인 구조를 가지는데, 고양이종 범주는 포유류강 범주의 하위 범주에 속한다.
◦
이는 얼룩고양이라는 고양이속 고양이종의 특성을 추론할 수 있게 한다.
◦
모든 포유류에게는 척추가 있고 새끼를 낳아 젖을 먹여 기른다는 특징을 공유하므로 포유류의 특징을 알고 있고 포유류강의 하위 범주에 고양이종이 있음을 알고 있다면 얼룩고양이를 실제로 관찰, 분석하지 않고도 척추가 있고 새끼를 낳아 젖을 먹여 기른다는 사실을 쉽게 추론할 수 있다.
•
린네의 계층 구조는 좀 더 세부적인 범주가 계층의 하위에 위치하고 좀 더 일반적인 범주가 계층의 상위에 위치한다.
•
이때 계층의 상위에 위치한 범주를 계층의 하위에 위치한 범주의 일반화라고 하고 계층의 하위에 위치한 범주는 계층의 상위에 위치한 범주의 특수화라고 한다.
서브타입
•
객체지향의 세계에서 범주는 개념을 의미하고 개념은 타입을 의미하므로 일반화와 특수화는 계층 구조 안에 존재하는 타입 간의 관계를 의미한다.
•
즉, 일반적인 타입을 이용해 좀 더 세부적인 타입을 정의함으로써 타입 간의 계층 구조를 구축할 수 있다.
•
이때 어떤 타입이 다른 타입보다 더 일반적이라면 이 타입을 슈퍼타입, 좀 더 특수하다면 이 타입을 서브타입이라고 한다.
◦
각각 슈퍼타입은 서브타입의 일반화, 서브타입은 슈퍼타입의 특수화라고 한다.
•
린네의 분류 체계는 아리스토텔레스의 분류법에 기원을 두고 있는데, 아리스토텔레스는 기존의 범주가 가진 속성을 새로운 범주가 포함할 경우, 새로운 범주는 기존의 범주를 확장하여 정의할 수 있다고 했다.
◦
즉, 새로운 범주의 속성은 자신이 정의한 본질적인 속성에 기존 범주의 본질적인 속성을 추가한 것이다.
•
이는 객체지향에도 동일하게 적용되는데, 일반화와 특수화의 계층 구조에서 서브타입은 슈퍼타입이 가진 본질적인 속성과 함께 자신만의 추가적인 속성을 가진다.
•
내연의 관점에서 슈퍼타입의 정의가 서브타입의 정의보다 더 일반적이라는 것이다.
◦
이를 통해 우리는 논리적인 추론이 가능해진다. 서브타입은 슈퍼타입의 본질적인 속성을 모두 포함해야 하기 때문에 계층에 속하는 모든 서브타입이 슈퍼타입의 속성을 공유한다는 것을 쉽게 예상할 수 있다.
•
객체의 집합을 나타내는 외연의 관점에서 서브타입은 슈퍼타입의 부분집합으로 표현된다.
•
크레이그 라만은 어떤 타입이 다른 타입의 서브타입이 되기 위해서는 100% 규칙과 Is-a 규칙을 준수해야 한다고 했다.
◦
100% 규칙은 타입의 내연과 관련된 규칙으로 정의는 다음과 같다. 슈퍼타입의 정의가 100% 서브타입에 적용되어야만 한다. 서브타입은 속성과 연관관계 면에서 슈퍼타입과 100% 일치해야 한다.
◦
Is-a 규칙은 타입의 외연과 관련된 규칙으로 정의는 다음과 같다. 서브타입의 모든 인스턴스는 슈퍼타입 집합에 포함되어야 한다. 이는 대개 영어로 서브타입은 슈퍼타입이다(subtype is a supertype)이라는 구문을 이용해 쉽게 테스트할 수 있다.
•
Is-a 규칙에서 알 수 있는 것처럼 흔히 일반화 관계는 is-a 관계라고 한다.
◦
고양이는 육식동물이다, 육식동물은 고양이다라는 말은 각각 두 가지 범주 간의 일반화 관계를 표현한 것이다.
◦
어느것이 말이 되는가?
상속
•
이런 일반화, 특수화 관계를 프로그래밍 언어를 이용해 구현하는 가장 일반적인 방법은 클래스 간의 상속을 사용하는 것이다.
◦
그러나 안타깝게도 모든 상속 관계가 일반화 관계인 것은 아니다. 프로그램 내의 두 클래스 간에 상속 관계가 존재할 때 이 관계를 반드시 일반화 관계라고 할 수 없기 때문이다.
•
일반화의 원칙은 한 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼타입에 순응해야 한다.
•
순응에는 구조적인 순응과 행위적인 순응 두 가지 종류가 존재하는데, 두 가지 모두 특정 기대 집합에 대해 서브타입이 슈퍼타입을 대체할 수 있음인 대체 가능성을 의미한다.
•
구조적인 순응의 경우, 기대 집합은 속성과 연관관계에 관한 것이며 행위적인 순응의 경우, 기대 집합은 행위가 동일한 계약을 기반으로 하느냐에 관한 것이다.
◦
구조적인 순응은 타입의 내연과 관련된 100% 규칙을 의미하는데, 즉 서브타입은 슈퍼타입이 가지고 있는 속성과 연관관계 면에서 100% 일치해야 한다.
◦
따라서 서브타입이 슈퍼타입을 대체하더라도 구조에 관한 동일한 기대 집합을 만족시킬 수 있어야 한다.
◦
Person이 name이라는 속성을 가진다면 Person의 서브타입인 Employee 역시 name이라는 속성을 가질 것이라고 기대할 수 있다. 따라서 Employee는 Person에 구조적으로 순응하며 Person을 대체할 수 있다.
•
행위적인 순응은 타입의 행위에 관한 것이며 서브타입은 슈퍼타입을 행위적으로 대체 가능해야 한다.
◦
행위적인 순응을 흔히 리스코프 치환 원칙이라고 한다.
◦
Perosn이 getAge()라는 메시지에 대한 응답으로 나이를 반환한다면 Employee 역시 getAge()라는 메시지에 대해 나이를 반환해야 한다. 이는 클라이언트의 입장에서 Employee가 Person의 행위적으로 순응하기 때문에 대체 가능하다.
•
상속은 서브타이핑과 서브클래싱의 두 가지 용도로 사용될 수 있다.
◦
서브클래스가 슈퍼클래스를 대체할 수 있는 경우 이를 서브타이핑이라고 한다. 없는 경우를 서브클래스라고 한다.
◦
서브타이핑의 경우 설계의 유연성이 목표인 반면 서브클래싱은 상속의 또 다른 용도인 코드의 중복 제거와 재사용성이 목적이다.
◦
흔히 서브타이핑을 인터페이스 상속이라고 하고 서브클래싱을 구현 상속이라고 한다.
•
따라서 우리는 어떤 클래스가 상속을 했다는 이유만으로 두 클래스 간의 관계가 서브타이핑인지 서브클래싱인지 알 수 없다.
•
서브타이핑의 전제 조건이 대체가능성이기 때문에 서브타이핑인지 여부를 확인하려면 클라이언트 관점에서 실제로 어떻게 사용되고 있는지를 확인해야 한다.
•
여러 클래스들의 계층으로 구성된 상속 계층에서 수신된 메시지를 이해하는 기본적인 방법은 클래스 간의 위임을 사용하는 것으로 자식 클래스가 메시지를 이해하지 못한다면 해당 자식의 부모 클래스로 메시지를 위임한다. 이는 재귀적으로 처리할 수 있을때까지 반복된다.
◦
프로토타입 기반 언어에서도 동일하게 위임 메커니즘에 의해 처리된다. 단지 자식 객체와 부모 객체 사이에 위임이 이뤄진다는 점이다.
◦
위임이 클래스를 기준으로 이뤄지는지, 객체를 기준으로 이뤄지는지 여부에 따라 차이점이 생길 뿐이다.
집합과 분해
계층적인 복잡성
•
허버트 사이먼은 자신의 저서 복잡성의 구조에서 호라와 템프스라는 두 명의 시계 장인에 관한 우화를 하나 소개하는데, 이는 다음과 같다.
◦
템프스는 한 번에 하나의 시계를 바로 제작하기 때문에 실수가 발생하는 경우, 시계를 처음부터 다시 만들어야 했다.
◦
호라는 시계를 단계적으로 만들어나갔기 때문에 실수가 발생하더라도 그 오류의 전파 범위가 작았다.
•
결과적으로 호라는 번창한 반면 템프스는 망했다. 여기서 얻을 수 있는 교훈은 다음과 같다.
◦
복잡성은 계층의 형태를 띤다.
◦
단순한 형태로부터 복잡한 형태로 진화하는데 걸리는 시간은 그 사이에 존재하는 안정적인 형태의 수와 분포에 의존한다.
•
이처럼 안정적인 형태의 부분으로 전체를 구축하는 행위를 집합이라고 하며 전체를 부분으로 분할하는 행위를 분해라고 한다.
◦
집합은 불필요한 세부 사항을 배제하고 큰 그림에서 대상을 다룰 수 있게 한다.
•
집합은 전체의 내부로 불필요한 세부 사항을 감춰주기 때문에 추상화 메커니즘인 동시에 캡슐화 메커니즘이다.
•
집합의 경계가 이처럼 명확한 경우도 있지만 모호한 경우도 굉장히 많은데, 그럼에도 사람들은 이 경계를 결정하는데 큰 어려우을 느끼지 않는다.
◦
이는 인간이 본능적으로 세계를 안과 밖 지향성을 가진 그릇으로 보기 때문이다.
◦
즉, 실제로 경계가 존재하지 않아도 쉽게 추상적인 경계를 찾을 수 있기 때문이다.
▪
이는 조지 레이코프의 책, 살믕로서의 은유에서 인간이 본능적으로 그릇 은유를 가지고 있다고 설명한데서 가져오신 내용같다.
◦
그릇 은유를 통해서 사람들은 경계가 존재하지 않는 곳에서도 인위적인 집합을 수월하게 창조해낼 수 있다.
합성 관계
•
객체와 객체 사이의 전체-부분 관계를 구현하기 위해서는 합성 관계를 사용한다.
•
합성 관계는 부분을 전체 안에 숨겨 캡슐화함으로써 인지 과부화를 방지한다.
◦
예를 들어 주문 항목은 주문의 일부이므로 이 모델을 다루는 사람은 주문 항목과 관련된 세부 사항은 개의치 않고 주문과 상품만이 존재하는 것처럼 모델을 다룰 수 있다.
•
전체-부분의 관계가 아니라 단지 서로 연결되어 있는 경우는 연관 관계를 사용한다.
◦
상품과 주문의 경우가 그러하다. 주문은 상품을 포함하고 있지 않지만 상품과 관계를 맺고 있다.
•
이 관계들이 항상 차이가 명확하다고 할 수는 없으나 일반적으로 합성 관계로 연결된 객체는 포함하는 객체가 제거될 때 내부 객체도 함께 제거되는 편이다.
•
이에 반해 연관 관계로 연결된 두 객체는 생명주기와 관련된 그 어떤 제약도 부과하지 않는다. 이 관계에 있는 두 객체는 독립적으로 제거될 수 있다.
•
이는 합성 관계가 생명주기 측면에서 연관 관계보다 객체들을 더 강력하게 결합함을 의미한다.
패키지
•
클라이언트나 조금 더 밑의 수준에서 바라본 소프트웨어는 관련된 클래스 집합들을 하나의 논리적인 단위로 묶는 구성 요소로 패키지 또는 모듈이라고 한다.
•
패키지를 이용하면 구조를 이해하기 위해 한 번에 고려해야 하는 요소의 수를 줄일 수 있다. 또한 클래스 각각이 아닌 집합을 캡슐화함으로써 전체적인 복잡도를 낮출 수 있다.