타입과 추상화
개념
개념(concept)이란 공통점을 기반으로 객체들을 하나로 묶기 위한 그릇이다. 개념을 이용하면 서로 다른 객체를 여러 그룹으로 분류할 수 있다.
이렇게 분류된 그룹 안에 속한 하나의 객체를 그 개념의 인스턴스 (instance) 라고 한다.
즉, 객체는 특정 개념에 적용되었을 때 ‘인스턴스’라고 부를 수 있다.
따라서 우리가 많은 사물들의 공통점을 통해 개념을 확립하고, 분류하였을 때 비로소 하나의 객체는 인스턴스로 사용될 수 있는 것이다.
개념은 세 가지 관점으로 해석할 수 있다.
- 심볼 (symbol) - 개념을 지칭하는 명칭
- 내연 (intension) - 개념의 정의
- 외연 (extension) - 개념에 속하는 모든 객체의 집합
타입(type)
객체를 분류함으로써 우리는 객체들의 복잡성을 극복할 수 있다.
소프트웨어에서는 이 ‘개념’이라는 것을 타입이라는 용어로 지칭한다. 사람들은 데이터의 용도와 행동에 따라 적절히 분류하여 데이터 타입이라는 체계를 확립하였고, 0과 1로 이루어져 있는 시스템을 타입 시스템으로 단순화한 것이다.
타입의 특징은 아래와 같다.
- 타입은 데이터가 사용되는 방법에 따라 분류된다. 즉 어떤 연산자가 적용되냐에 따라 데이터의 타입이 달라지게 된다.
- 타입에 속한 데이터를 메모리에 표현하는 방식은 철저하게 감춰진다.
그렇다면 객체의 입장에서 타입을 설명해보자.
객체에게 중요한 것은 행동이다. 객체는 데이터가 아닌 애플리케이션의 목적을 달성하기 위해 행동을 수행할 뿐이다. 타입의 특징에 따라, 객체가 어떤 행동을 수행하느냐에 따라 객체의 타입이 달라진다. 또한 객체의 내부적인 표현은 외부로부터 감춰진다.
다형성과 캡슐화
같은 행동을 수행하는 객체는 같은 타입을 가질 수 있다.
이에 따르면 같은 타입의 객체가 서로 다른 데이터를 가질 수 있고, 외부로부터 동일한 메시지를 수신하고 처리할 수 있다. 그러나 어쨌든, 서로 다른 객체이므로 내부 구현이 다르기에 메시지를 처리하는 방식은 다를 수 있다.
이는 동일한 요청에 다양한 방법으로 반응할 수 있는 다형성이라는 특징을 객체지향에게 부여한다.
그리고 객체는 행동으로만 타입을 정하므로, 외부로부터 데이터를 감춰야한다. 이를 캡슐화라고 한다.
일반화와 특수화
촉촉한 초코칩은 다른 초코칩과 다르게 촉촉하다*라는 성질을 포함한다. 허쉬 초콜릿칩 쿠키는 다른 초코칩과는 다르게 허쉬 초콜릿으로 만들어졌다.
둘은 같은 초코칩임에도 서로 다른 특수한 성질을 가지고 있다. 촉촉한 초코칩, 허쉬 초콜릿 칩 쿠키를 하나의 [초코칩]으로 묶는 과정을 일반화 라고 하고, 그 반대로 특수한 기능을 붙여서 [촉촉한 초코칩]과 같이 좁은 범위를 표현하는 것을 특수화라고 한다.
일반화, 특수화의 관계를 결정하는 것은 객체의 행동이다. 즉 상태만으로 일반화와 특수화의 관계를 결정지을 수 없다.
일반적인 타입을 슈퍼 타입(Supertype), 특수한 타입을 서브 타입(Subtype)이라고 한다.
동적 모델과 정적 모델
추상화된 타입을 통해 객체는 시간과 상태 변화라는 불필요한 요소를 제거하고 오로지 정적인 하나의 물체로서 존재할 수 있게 되었다.
이렇게 객체가 가질 수 있는 모든 상태와 행동을 시간과 무관하게 표현하는 모델을 타입 모델 혹은 정적 모델이라고 한다.
반대로 객체의 스냅샷(snapshot, 혹은 객체 다이어그램)을 통해 특정 시점에 어떤 상태인지 포착하는 것을 동적 모델이라고 한다.
역할, 책임, 협력
현실 세계에서 인간의 행동을 결정하는 것은 처해 있는 상황, 문맥이다.이러한 문맥은 타인과의 협력 속에서 만들어진다. 객체를 설계할 때는 개별적인 객체가 아닌 객체들 간에 집중해야한다.
협력
협력은 한 사람의 요청과 다른 사람의 응답으로 이루어진다. A가 B에게 요청을 보내면 B는 그 요청을 수행할 의무가 있다. 즉 요청과 응답은 협력에 참여하는 객체의 책임을 정의한다.
책임
책임은 객체에 의해 정의되는 응집도 있는 행위의 집합으로, 객체가 알고 있는 것과 할 수 있는 것으로 구성된다.
이를 다르게 말하면 객체는 외부에게 정보 (아는 것)을 제공해주거나 서비스(할 수 있는 것)을 제공해주는 역할을 한다.
이러한 책임이 수행되는 시점은 요청이 전송되는 것이다. 협력을 요청하는 쪽을 송신자, 메시지를 받아 처리하는 쪽을 수신자라고 한다.
메시지는 하나의 책임이 여러 협력 관계로서 전달되는 매개체이다.
역할
여러 객체가 각자의 책임을 수행하고 있다. 이는 협력 관계에 있는 객체에게 메시지를 보냄으로써 수행된다. ‘밥을 한다’라는 책임이 있다면 이 책임을 여러 객체가 동시에 지고 있을 수 있다. 이렇게 협력 관계 내에서 다른 객체로 대체할 수 있으므로 밥을 하는 사람이라는 역할이라고 명명할 수 있다.
즉 역할이라는 개념으로 책임을 추상화하여 유연하고 단순하고 재사용성이 높은 설계를 할 수 있는 것이다.
역할은 이렇게 하나의 협력 안에 여러 객체가 참여할 수 있도록 협력을 추상화하는 것이다. 주어진 역할에 특정 객체가 대체되기 위해서는 객체가 협력 안에서 역할이 수행하는 모든 책임을 수행할 수 있어야 한다.
추가로, 특정 역할에 해당하는 객체는 그 역할이 암시하는 책임보다 더 다양한 책임을 가지고 있을 수 있다. 따라서 객체의 타입과 역할 사이에는 일반화, 특수화 관계가 성립한다.
객체의 모양을 결정하는 협력
객체지향의 핵심은 객체가 협력 안에서 어떤 책임과 역할을 수행할 것인지를 결정하는 것이다. 따라서 객체는 단순히 데이터를 저장하기 위한 용도가 아닌 객체의 행동, 책임을 수행하기 위해 존재한다.
협력이라는 문맥에서 객체가 수행해야 할 적절한 책임 (행동)을 정의하고 그 행동을 수행하는 데 필요한 데이터를 고민한다. 이를 통해 객체가 충분히 자율적인 동시에 충분히 협력적인 객체를 창조할 수 있다.
이렇게 역할, 책임, 협력의 관점에서 애플리케이션을 효과적으로 설계하기 위한 기법을 세 가지 소개하겠다.
책임-주도 설계 (Responsibility-Driven Design)
객체지향 설계란 애플리케이션 기능을 구현하기 위한 협력 관계를 결정짓고, 협력에 필요한 역할과 책임을 정의해 이를 수행할 수 있는 객체를 식별하는 과정이다.
책임 주도 설계를 수행하는 과정은 아래와 같다.
- 시스템이 사용자에게 제공해주어야 하는 기능을 파악한다.
- 시스템의 책임을 작게 분할한다.
- 분할된 책임을 적절한 객체, 혹은 역할에게 할당한다.
- 이 객체가 책임을 수행하는 중 다른 객체와의 협력이 필요하면, 협력과 관련한 책임을 그 객체에게 할당한다.
디자인 패턴(Design Pattern)
디자인 패턴은 책임-주도 설계의 결과로 만들어지는 것이다. 디자인 패턴은 일반적으로 발생하는 문제와 그 해법의 쌍으로 구성된다. 대표적인 Composite 디자인 패턴의 구조를 살펴보면, 구성 요소가 역할, 책임, 협력 관계로 구성되어있음을 확인할 수 있다.
Component는 공용 인터페이스를 정의하여 클라이언트와 협력한다. 클라이언트가 Component에게 협력을 요청하는 메시지를 보내면 Component는 Composite와 Leaf를 통해 결괏값을 반환하는 역할을 한다. Leaf는 공용 인터페이스를 동작시키는 호출에 응답하는 역할을 하며 Composite는 외부로부터 세부 사항을 감추고 포함된 부분을 하나의 단위로 행동하는 역할을 한다.
테스트-주도 개발(Test-Driven Design, TDD)
테스트-주도 개발은 객체가 어떤 메시지를 수신할 때 어떤 결과를 반환하고 어떤 객체와 협력할 지에 대한 기대를 코드로 작성하는 것이다. 즉 TDD는 테스트라는 안전장치를 통해 더 빠르고 견고한 방법으로 책임-주도 설계의 프로세스를 달성하는 방법이다.
'DEV book > 객체지향의 사실과 오해' 카테고리의 다른 글
[OOP] 객체지향의 사실과 오해 5, 6장 - 자율적인 객체와 도메인 모델링 (1) | 2024.02.06 |
---|---|
[OOP] 객체지향의 사실과 오해 1, 2장 리뷰 - 객체지향의 본질 (0) | 2024.01.31 |