객체지향의 사실과 오해

동일한 요청에 대해 서로 다른 방식으로 응답할 수 있는 능력을 다형성이라고 한다.

객체란 인간이 분명하게 인지하고 구별할 수 있는 물리적인 또는 개념적인 경계를 지닌 어떤 것이다.

상태 행동 식별자

‘상태’는 특정 시점에 객체가 가지고 있는 정보의 집합으로 객체의 구조적 특징을 표현한다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티 값으로 구성된다. 객체의 프로퍼티는 단순한 값과 다른 객체를 참조하는 링크로 구분할 수 있다.

상태를 외부에 노출시키지 않고 행동을 경계로 캡슐화하는 것은 결과적으로 객체의 자율성을 높인다.

‘행동’이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달할 수 있다. 객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동은 외부에 가시적이어야 한다.

객체에 접근할 수 있는 유일한 방법은 객체가 제공하는 행동뿐이다. 따라서 행동을 통해서만 상태에 접근할 수 있다는 점은 객체의 캡슐화를 강조한다.

상태를 먼저 결정하고 행동을 나중에 결정하는 방법은 설계에 나쁜 영향을 끼친다. 훌륭한 객체를 만들기 위해서는 상태가 아니라 행동에 초점을 맞춰야 한다. 즉, 행동이 상태를 결정한다.

값과 객체의 가장 큰 차이점은 값은 식별자를 가지지 않지만 객체는 식별자를 가진다는 점이다. 값은 오직 상태만을 이용해 동등성을 판단하기 때문에 인스턴스를 구별하기 위한 별도의 식별자를 필요로 하지 않는다. 하지만 객체는 시간에 따라 변경되는 상태를 포함하며, 행동을 통해 상태를 변경한다. 따라서 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.

객체지향이란 현실 세계의 모방,추상화 라고 하는데 그 안에는 현실 세계를 모방해서 단순화한다는 의미가 숨어 있다. 그러나 객체지향 세계는 현실 세계의 단순한 모방이 아니다. 그렇다면 현실 속의 객체와 소프트웨어 객체 사이의 가장 큰 차이점은 무엇일까? 그것은 현실 속에서는 수동적인 존재가 소프트웨어 객체로 구현될 때는 능동적으로 변한다는 것이다(ex 음료 객체의 수량 상태의 변화는 음료 객체가 책임진다). 객체지향의 세계에서 모든 객체는 자신의 상태를 스스로 관리하는 자율적인 존재다.

추상화

  1. 목적에 맞게 현실을 분해하고 단순화하는 전략

  2. 불필요한 부분을 무시함으로써 현실에 존재하는 복잡성을 극복하는것

  3. 추상화의 수준, 이익 가치는 목적에 의존적이다.

  4. 알아야하는 사실만 정확하게 표현하고 몰라도 되는 정보는 무시함으로써 이해하기쉽고 단순하며 목적에 부합하는 결과를 도출한다.

이 책에서는 추상화를 다음과 같이 정의한다. 어떤 양상, 세부사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다. 복잡성을 다루기 위해 추상화하는 두 차원에서 이뤄진다.

  1. 구체적인 사물들간의 공통점은 취하고 차이점은 버리는 일반화를 통해 단순하게 만드는 것이다.

  2. 중요한 부분을 강조하기 위해 불필요한 세부사항을 제거함으로써 단순하게 만드는것이다.

모든 경우에 추상화의 목적은, 복잡성을 이해하기 쉬운 수준으로 단순화하는 것이라는 점을 기억하라

객체와 타입

우리는 객체를 일종의 데이터처럼 사용한다. 그렇다면 객체는 데이터인가? 그렇지 않다. 객체에서 중요한 것은 객체의 행동이다. 상태는 행동의 결과로 초래된 부수효과를 쉽게 표현하기 위해 도입한 추상적인 개념일 뿐이다. 객체를 창조할 때 가장 중요하게 고려해야 하는 것은 객체가 이웃하는 객체와 협력하기 위해 어떤 행동을 해야 할지를 결정하는 것이다. 즉, 객체가 협력을 위해 어떤 책임을 지녀야 하는지를 결정하는 것이 객체지향 설계의 핵심이다.

클래스

객체지향 프로그래밍 언어에서 정적인 모델은 클래스를 이용해 구현된다. 따라서 타입을 구현하는 가장 보편적인 방법은 클래스를 이용하는 것이다. 클래스와 타입은 동일한 것이 아니다. 타입은 객체를 분류하기 위해 사용하는 개념이다. 반면 클래스는 단지 타입을 구현할 수 있는 여러 구현 메커니즘 중 하나일 뿐이다.

객체를 분류하는 기준은 타입이며, 타입을 나누는 기준은 객체가 수행하는 행동이다. 객체를 분류 하기 위해 타입을 결정한 후 프로그래밍 언어를 이용해 타입을 구현할 수 있는 한 가지 방법이 클래스다.

역할

동일한 역할을 수행할 수 있다는 것은 해당 객체들이 협력 내에서 동일한 책임의 집합을 수행할 수 있다는 것을 의미한다. 동일한 역할을 수행하는 객체들이 동일한 메시지를 수신할 수 있기 때문에 동일한 책임을 수행할 수 있다는 것은 매우 중요한 개념이다.

객체지향에 대한 선입견

많은 사람들은 시스템에 필요한 데이터를 저장하기 위해 객체가 존재한다고 선입견을 가지고 있다. 그러나 객체가 존재하는 이유는 행위를 수행하며 협력에 참여하기 위해서다. 두 번째 선입견은 객체지향이 클래스와 클래스 간의 관계를 표현하는 시스템의 정적인 측면에 중점을 둔다는 것이다. 클래스는 단지 시스템에 필요한 객체를 표현하고 생성하기 위해 프로그래밍 언어가 제공하는 구현 메커니즘이라는 사실을 기억하라. 객체지향의 핵심은 클래스를 어떻게 구현할 것인가가 아니라 객체가 협력 안에서 어떤 책임과 역할을 수행할 것인지를 결정하는 것이다.

다형성

서로 다른 객체들이 다형성을 만족시킨다는 것은 객체들이 동일한 책임을 공유한다는 것을 의미한다. 다형성에서 중요한 것은 메시지 송신자의 관점이다. 메시지 수신자들이 동일한 오퍼레이션을 서로 다른 방식으로 처리하더라도 메시지 송신자의 관점에서 이 객체들은 동일한 책임을 수행하는 것이다.

메시지가 인터페이스를 결정한다.

객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 '메시지 전송'이다. 따라서 객체의 인터페이스는 객체가 수신할 수 있는 메시지의 목록으로 구성된다.

도메인 모델링과 유스케이스

도메인 모델이란 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다. 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 텍스트다. 유스케이스는 다이어그램이 아니다. 다이어그램에 노력을 쏟지 말라. 유스케이스는 하나의 시나리오가 아니라 여러 시나리오들의 집합이다.

유스케이스는 사용자에게 제공할 기능을 시스템의 책임으로 보게 함으로써 객체 간의 안정적인 구조에 책임을 분배할 수 있는 출발점을 제공한다. 도메인 모델은 기능을 수용하기 위해 은유할 수 있는 안정적인 구조를 제공한다. 책임-주도 설계는 유스케이스로부터 첫 번째 메시지와 사용자가 달성하려는 목표를, 도메인 모델로부터 기능을 수용할 수 있는 안정적인 구조를 제공받아 실제로 동작하는 객체들의 협력 공동체를 창조한다.

7장 함께 모으기

도메인이란 사용자들이 관심을 가지고 있는 특정분야나 주제를 말하며 SW는 도메인에 존재하는 문제를 해결하기 위해 개발된다.

객체지향 설계

개념 관점 : 설계는 도메인 안에 존재하는 개념과 개념들 사이에 관계를 표현한다. 명세 관점 : 사용자 영역인 도메인을 벗어나 개발자 영역인 SW로 초점이 옮겨진다. 구현 관점 : 구현 관점의 초점은 객체들이 책임을 수행하는데 필요한 동작하는 코드를 작성하는 것이다.

이것은 동일한 클래스를 세 가지 다른 방향에서 바라보는 것을 의미한다.

객체지향 설계의 첫 번째 목표는 훌륭한 객체를 설계하는 것이 아니라 훌륭한 협력을 설계하는 것이다. 협력을 설계할 때는 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 한다.

상속

프로그래밍 언어를 이용해 일반화와 특수화 관계를 구현하는 가장 일반적인 방법은 클래스 간의 상속을 사용하는 것이다. 일반화의 원칙은 한 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼 타입에 순응해야 한다는 것인데, 순응에는 구조적인 순응과 행위적인 순응 두 가지 종류가 있다.

구조적인 순응은 타입의 내연과 관련된 100% 규칙을 의미한다. 즉, 서브타입은 슈퍼타입이 가지고 있는 속성과 연관관계 면에서 100% 일치해야 한다. 예를 들어 Person이 name이라는 속성을 가진다면 Person의 서브타입인 Employee 역시 name이라는 속성을 가질 것이라고 기대할 수 있다.

행위적인 순응은 타입의 행위에 관한 것이며, 서브타입은 슈퍼타입을 행위적으로 대체 가능해야 한다. 행위적인 순응을 흔히 리스코프 치환 원칙이라고 한다. Person이 getAge()라는 메시지에 대한 응답으로 나이를 반환한다면 서브타입인 Employee 역시 getAge()라는 메시지에 대한 응답으로 나이를 반환해야 한다. 클라이언트 입장에서 Employee는 Person에 대해 행위적으로 순응하기 때문에 대체 가능하다.

Last updated