추상화?
추상화는 도메인의 복잡성을 단순화하고 직관적인 멘탈 모델을 만드는 데 사용할 수 있는 가장 기본적인 인지 수단이다.
- 분류와 인스턴스화
- 일반화와 특수화
- 집합과 분해
객체지향의 가장 큰 장점은 동일한 추상화 기법을 프로그램의 분석, 설계, 구현 단계에 걸쳐 일관성 있게 적용할 수 있다는 점이다.
분류와 인스턴스화
개념과 범주
개념: 속성과 행위가 유사한 객체에 공통적으로 적용되는 관념이나 아이디어
뷴류(범주로 묶는 것): 객체들의 특정 집합에 공통의 개념을 적용하는 것
(ex. 자동자 범주에 적용되는 개념: 바퀴를 이용해 사람을 운반하는 운송수단)
분류를 통해 개별 현상(객체)을 하나의 개념(타입)으로 다룰 수 있다.
즉, 분류는 객체와 타입 간의 관계를 나타낸 것이다.
분류의 역은 타입에 해당하는 객체를 생성하는 과정인 인스턴스화
어떤 객체가 타입의 정의에 부합할 경우 그 객체는 자동으로 해당 타입의 인스턴스가 된다.
타입 ( = 개념)
객체지향에서 개념을 가리키는 표준 용어는 타입이다.
타입을 객체의 분류 장치로서 적용할 수 있으려면 세 가지 관점에서의 정의가 필요하다.
- 심볼: 타입을 가리키는 간략한 이름이나 명칭
ex. 자동차 - 내연: 타입의 완전한 정의. 내연의 의미를 이용해 객체가 타입에 속하는지 여부를 확인할 수 있다.
ex. 바퀴를 이용해 사람을 운반하는 운송수단 - 외연: 타입에 속하는 모든 객체들의 집합
ex. 승용차, 버스, 트럭
외연과 집합
타입의 외연 = 타입에 속하는 객체들의 집합
집합은 외연을 가리키는 또 다른 명칭이다.
객체들을 분류할 때 교집합이 발생할 수도 있다.
객체들은 동시에 서로 다른 집합에 포함될 수 있다.
(ex. 어떤 컴퓨터는 데스크톱 컴퓨터인 동시에 사무용 컴퓨터일 수 있다.)
단일 분류: 한 객체가 한 시점에 하나의 타입에만 속하는 것
다중 분류: 한 객체가 한 시점에 여러 타입에 속하는 것
대부분의 객체지향 프로그래밍 언어들은 단일 분류만을 지원한다.
대부분의 언어에서 한 객체는 오직 한 클래스의 인스턴스여야만 한다.
다중 분류는 다중 상속과 다르다.
다중 상속: 하나의 타입이 다수의 슈퍼타입을 가질 수 있도록 허용. 타입 정의를 생략할 수는 없음.
다중 분류: 특정한 타입을 정의하지 않고도 하나의 객체가 서로 다른 타입의 인스턴스가 되도록 허용.
객체가 타입을 변경할 수 있다면 어떻게 될까?
(ex. 한 컴퓨터는 시간의 흐름에 다라 교육용 컴퓨터 -> 사무용 컴퓨터로 분류가 바뀔 수 있다.)
동적 분류: 객체가 한 집합에서 다른 집합의 원소로 자신이 속하는 타입을 변경할 수 있는 경우
정적 분류: 객체가 자신의 타입을 변경할 수 없는 경우
다중 분류와 동적 분류는 개념적인 관점에서 도메인을 분석하는 데는 유용하지만,
객체지향 프로그래밍 언어의 제약으로 인해 이를 구현으로 옮기기는 쉽지 않다.
제일 현실적인 방법은 다음과 같다.
1. 다중 분류와 동적 분류 관점에서 도메인 모델의 초안 만들기
2. 실제 구현에 적합하도록 단일 분류와 정적 분류 방식으로 객체들의 범주 재조정하기
클래스
객체지향 프로그래밍 언어로 타입을 구현하는 가장 보편적인 방법은 클래스를 이용하는 것이다. (클래스 != 타입)
클래스 말고 추상 클래스나 인터페이스로 타입을 구현할 수도 있다.
아리스토텔레스는 객체의 특성을 2가지로 분류했다.
본질: 한 사물의 가장 핵심적이고 필수불가결한 속성
우연: 본질적이지 않은 속성
(ex. 어떤 사람이 회사원이 됐다고 해도 여전히 사람이다.)
클래스는 객체가 공유하는 본질적인 속성을 정의한다.
대부분의 객체지향 프로그래밍 언어에서 동일한 범주에 속하는 객체는 동일한 클래스의 인스턴스여야 한다.
본질적인 속성은 표현할 수 있지만 우연적인 속성은 표현할 수 없다.
즉, 동일한 번주의 객체는 동일한 속성을 가져야 한다.
프로토타입 언어에서 분류와 인스턴스화는 프로토타입이라는 객체의 복사를 통해 이뤄진다.
일반화와 특수화
범주의 계층
카를로스 린네의 생물 분류 체계는 범주 간의 계층적인 구조를 가진다.
(ex. 얼룩고양이는 동물계 척색동물문 포유류강 육식동물목 고양이과 고양이속 고양이종의 하위 종)
세부적인 범주가 계층의 하위에, 일반적인 범주가 계층의 상위에 위치한다.
상위 범주를 하위 범주의 일반화, 하위 범주를 상위 범주의 특수화로 볼 수 있다.
서브타입
일반화와 특수화는 계층 구조 안에 존재하는 타입 간의 관계를 의미한다.
일반적인 타입을 이용해 세부적인 타입을 정의함으로써 타입 간의 계층 구조를 구축할 수 있다.
슈퍼타입: 일반적인 타입. 서브타입의 일반화
서브타입: 특수한 타입. 슈퍼타입의 특수화
서브타입 = 슈퍼타입의 본질적인 속성 + 자신만의 추가적인 속성
내연의 관점에서 슈퍼타입의 정의가 서브타입의 정의보다 더 일반적이다.
어떤 타입이 다른 타입의 서브타입이 되기 위한 크레이그 라만의 규칙
- 100% 규칙: 타입의 내연과 관련된 규칙
슈퍼타입의 정의가 100% 서브타입에 적용돼야만 한다.
(ex. 고양이는 육식동물과 포유류가 가진 속성을 포함한다.) - Is-a 규칙: 타입의 외연과 관련된 규칙
서브타입의 모든 인스턴스는 슈퍼타입 집합에 포함돼야 한다. (subtype is a supertype)
(ex. 고양이는 육식동물이다.)
상속
일반화와 특수화 관계를 구현하는 가장 일반적인 방법은 클래스 간의 상속을 사용하는 것이다.
모든 상속 관계가 일반화 관계인 것은 아니다.
한 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼타입에 순응해야 한다.
- 구조적인 순응 (100% 규칙)
서브타입은 슈퍼타입의 속성에 대해 구조적으로 대체 가능해야 한다.
(ex. Person이 name 속성을 가진다면, Person의 서브타입인 Employee도 name 속성을 가진다.
Employee는 Person에 대해 구조적으로 순응하며 Person을 대체할 수 있다.) - 행위적인 순응 (리스코프 치환 원칙)
서브타입은 슈퍼타입을 행위적으로 대체 가능해야 한다.
(ex. Person이 getAge()라는 메시지에 대한 응답으로 나이를 반환한다면 Employee 역시 그래야 한다.
Employee는 Person에 대해 행위적으로 순응하기 때문에 Person을 대체할 수 있다.)
상속의 용도
- 서브타이핑 ( = 인터페이스 상속)
- 서브클래스가 슈퍼클래스를 대체할 수 있는 경우
- 목적: 설게의 유연성
- 서브클래싱 ( = 구현 상속)
- 서브클래스가 슈퍼클래스를 대체할 수 없는 경우
- 목적: 코드의 중복 제거와 재사용
모든 상속이 서브타이핑인 것은 아니다.
서브클래스가 슈퍼클래스를 대체할 수 없고 단지 코드만 공유하고 있다면 서브클래싱이다.
가능한 서브타이핑의 대체 가능성을 준수하도록 사용하자.
위임: 여러 클래스로 구성된 상속 계층에서 수신된 메시지를 이해하는 기본적인 방법
어떤 객체의 클래스가 수신된 메시지를 이해할 수 없다면 메시지를 부모 클래스로 위임한다.
위임 사슬은 메시지를 처리하거나 최상위 클래스에 도달할 때까지 계속된다.
프로토타입 기반 언어에서 상속은 객체와 객체 간의 관계로 이뤄진다.
위임은 자식 객체와 부모 객체 사이에서 이뤄진다.
집합과 분해
계층적인 복잡성
시계를 1000개의 부품으로 조립할 때, 1000개의 부품으로 한꺼번에 조립하는 것보다
1000개의 부품 중 10개를 모아 조립 부품을 만들고, 그 10개를 또 모아 큰 부품을 만드는 방식이 훨씬 효율적이다.
작은 부품 -> 안정적인 형태의 중간 부품 (연쇄적인 계층 구조)
- 복잡성은 계층의 형태를 띤다.
- 단순한 형태로부터 복잡한 형태로 진화하는 데 걸리는 시간은 그 사이에 존재하는 안정적인 형태의 수와 분포에 의존한다.
집합: 안정적인 형태의 부분으로부터 전체를 구축하는 행위
분해: 집합과 반대로 전체를 부분으로 분할하는 행위
집합은 불필요한 세부 사항을 추상화하여 복잡성을 줄여준다.
전체의 내부로 분필요한 세부 사항을 감춰주기 때문에 추상화 메커니즘이면서 캡슐화 메커니즘이다.
합성 관계
객체와 객체 사이의 전체-부분 관계를 구현하기 위해 사용한다.
상품을 몇 개 주문했는지를 가리켜 주문 항목이라 하자.
주문 항목을 주문과 합성 관계로 연결하면, 주문 항목의 존재를 감춤으로써 복잡성을 낮출 수 있다.
주문과 주문 항목은 합성 관계
- 주문과 상품만이 존재하는 것처럼 모델을 다룰 수 있다.
주문 항목과 상품은 연관 관계
- 주문 항목이 제거되어도 상품은 제거되지 않는다.
합성 관계는 생명주기 측면에서 연관 관계보다 더 강하게 객체들을 결합한다.
패키지 (모듈)
소프트웨어의 전체적인 구조를 표현하기 위해 관련된 클래스 집합을 하나의 논리적인 단위로 묶는 구성 요소
- 시스템의 전체 구조를 이해하기 위해 한 번 에 고려해야 하는 요소의 수를 줄일 수 있음
- 개별 클래스가 아닌 클래스의 집합을 캡슐화함으로써 전체적인 복잡도를 낮출 수 있음
- 응집도 높은 클래스 집합을 하나의 패키지 내부로 모으면 패키지 경계를 넘나들 필요가 적어짐
합성 관계: 내부에 포함된 객체들을 감춤으로써 내부 구조를 추상화
패키지: 내부에 포함된 클래스들을 감춤으로써 시스템 구조를 추상화