[책 내용 정리] 객체지향의 사실과 오해

June·2021년 3월 24일
0

책 요약 및 정리

목록 보기
1/6

1. 협력하는 객체들의 공동체

일반적으로 많은 사람들은 객체지향을 설명할 때 "객체지향이란 실세계를 직접적이고 직관적으로 모델링할 수 있는 패러다임"이라 한다.

하지만 실제로 애플리케이션을 개발하면서 객체제 직접적으로 대응되는 실세계의 사물을 마주할 일은 적지만, 실세계에 대한 비유가 객체지향의 다양한 측면을 이해하고 학습하는데 효과적이다.

역할, 책임, 협력

객체지향에서 가장 중요한 개념 세 가지이다.

요청과 응답으로 구성된 협력

일반적으로 하나의 문제를 해결하기 위해 다수의 사람 혹은 역할이 필요하여, 요청이 또 다른 사람에 대한 요청을 유발한다.

예를 들어 커피 주문이라는 협력은 손님이 캐시어에게 커피를 주문하고, 캐시어가 주문내역을 바리스타에게 전달하는 과정이다.

요청을 받은 사람은 주어진 책임을 다하면서 서비스를 제공한다. 즉 응답한다.

역할과 책임

사람들은 다른 사람과 협력하는 과정에서 역할을 부여받는다. '손님', '캐시어', '바리스타'.

어떤 객체도 섬이 아니다.

객체의 역할은 사람의 역활과 유사하게 다음과 같은 특징이 있다.

  • 여러 객체가 동일한 역할을 수행할 수 있다.
  • 역할은 대체 가능성을 의미한다.
  • 각 객체는 책임을 수행하는 방법을 자율적으로 선택할 수 있다. (다형성)
  • 하나의 객체가 동시에 여러 역할을 수행할 수 있다.

협력 속에 사는 객체

만약 애플리케이션의 내부를 볼 수 있다면, 메시지를 끊임 없이 주고 받으며 협력하는 객체들을 볼 수 있을 것이다.

객체의 두 가지 덕목이 있다.

  1. 객체는 충분히 '협력적'이어야한다. 다른 객체의 요청에 귀 기울이고, 다른 객체에게 도움을 요청해야 한다. 모든 것을 스스로 처리하려고 하는 전지전능한 객체는 자기파괴적이다.
  2. 객체는 충분히 '자율적'이어야 한다. 즉 자기 스스로의 원칙에 따르고, 통제하고 절제해야 한다.

흔히 객체를 '상태(State)''행동(behavior)'을 함께 지닌 실체라고 정의한다. 객체가 협력에 참여하는 과정 속에서 자율적인 존재로 남으려면 필요한 행동과 상태를 함께 지니고 있어야 한다.

객체의 자율성은 객체의 내부와 외부를 명확하게 구분하는 것에서 나온다. 객체는 다른 객체가 무엇을 수행하는지는 알 수 있지만 어떻게 수행하는지는 알 수 없다.

과거의 전통적인 개발 방법은 데이터와 프로세스를 엄격하게 구분한다. 이에 반해 객체지향에서는 데이터와 프로세스를 객체라는 하나의 틀 안에 함께 묶어 놓음으로써 객체의 자율성을 보장한다. 이것이 전통적인 개발 방법과 객체지향을 구분 짓는 가장 핵심적인 차이다.

협력과 메시지

객체지향의 세계에서 의사소통을 '메시지'라고 한다.

메서드와 자율성

객체가 수신된 메시지를 처리하는 방법을 메서드(method)라고 한다. 외부의 요청이 무엇인지를 표현하는 메시지와 요청을 처리하기 위한 구체적인 방법인 메서드를 분리하는 것은 객체의 자율성을 높이는 핵심 메커니즘이다. 이것은 캡슐화(encapsulation)라는 개념과도 깊이 관련돼 있다.

객체지향의 본질

  • 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바로보고 객체를 이용해 시스템을 분할하는 방법이다.

  • 자율적인 객체란 상태행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다.

  • 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다.

  • 객체는 다른 객체와 협력하기 위해 메시지를 전송하고, 메시지를 수신한 객체는 메시지를 처리하는데 적합한 메서드를 자율적으로 선택한다.

2. 이상한 나라의 객체

객체란 식별 가능한 개체 또는 사물이다. 객체는 자동차처럼 만질 수 있는 구체적인 사물일 수도 있고, 시간처럼 추상적인 개념일 수도 있다. 객체는 구별 가능한 식별자, 특징적인 행동, 변경 가능한 상태를 가진다. 소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다.

상태

상태를 이용하면 과거에 얽매이지 않고 현재를 기반으로 객체의 행동 방식을 이해할 수 있다. 상태는 근본적으로 세상의 복잡성을 완화하고 인지 과부하를 줄일 수 있는 중요한 개념이다.

여기서 앨리스와 음료는 객체다. 그러나 앨리스의 키와 위치, 음료의 양은 객체가 아닌 단순한 값이다. 모든 객체의 상태는 단순한 값과 객체의 조합으로 표현할 수 있다. 이때 객체의 상태를 구성하는 모든 특징을 통틀어 프로퍼티(property)라고 한다. 앨리스의 경우 키, 위치, 음료가 앨리스의 프로퍼티가 된다. 일반적으로 프로퍼티는 변경되지 않고 고정되기 때문에 '정적'이다. 반면 프로퍼티 값(property value)은 시간이 흐름에 따라 변경되기 때문에 '동적'이다. 앨리스의 키는 음료를 마시면 작아질 것이고, 문을 통과하면 위치가 정원으로 바뀔 것이고, 음료를 다마시면 음료를 버릴 것이다.

여기서는 앨리스와 음료 사이의 선이 사라졌다. 객체와 객체 사이의 의미 있는 연결을 링크(link)라고 한다. 객체와 객체 사이에는 링크가 존재해야만 요청을 보내고 받을 수 있다. 객체 간의 선으로 표현되는 링크와 달리 객체를 구성하는 단순한 값은 속성(attribute)라고 한다.

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

행동

  • 객체의 행동은 상태에 영향을 받는다.
  • 객체의 행동은 상태를 변경시킨다.

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

상태 캡슐화

객체지향의 세계에서 모든 객체는 자신의 상태를 스스로 관리하는 자율적인 존재다. 앨리스 객체의 키를 작게 만드는 것이 앨리스 자신인 것처럼 음료 객체의 양을 줄이는 것은 음료 자신이어야 한다. 음료의 양이 줄어들 것인지는 메시지를 수신한 음료가 결정할 사항이며, 앨리스와는 무관하다. 단지 앨리스는 음료의 양이 줄어들 것이라는 것을 믿고 요청을 전달할 뿐이다. 이것이 캡슐화이다.

식별자

값과 객체의 가장 큰 차이점은 값은 식별자를 가지지 않지만 객체는 식별자를 가진다는 점이다. 값(value)은 숫자, 문자열, 날짜, 시간, 금액 등과 같이 변하지 않는 양을 모델링한다. 흔히 값의 상태는 변하지 않기 때문에 불변 상태(immutable state)**를 가진다.

상태를 이용해 두 값이 같은지 판단할 수 있는 성징을 동등성(equality)라고 한다.

객체는 시간에 따라 변경되는 상태를 포함하며, 행동을 통해 상태를 변경한다. 따라서 객체는 가변 상태(mutable state)를 가진다. 객체 역시 상태와 무관하게 두 객체를 동일하거나 다르다고 판단할 수 있는 프로퍼티를 가진다. 식별자를 기반으로 객체가 같은지를 판단할 수 있는 성질을 동일성(identical)이라고 한다.

식별자란 어떤 객체를 다른 객체와 구분하는데 사용하는 객체의 프로퍼티이다. 값은 식별자를 가지지 않기 때문에 상태를 이용한 동등성 검사를 통해 두 인스턴스를 비교해야 한다. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.

  • 객체는 상태를 가지며 상태는 변경 가능하다.
  • 객체의 상태를 변경시키는 것은 객체의 행동이다.
    • 행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다.
    • 행동의 순서가 실행 결과에 영향을 미친다.
  • 객체는 어떤 상태에 있더라도 유일하게 식별 가능하다.

행동이 상태를 결정한다.

3. 타입과 추상화

추상화란 어떤 양상, 세부 사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다.

공통점을 기반으로 객체들을 묶기 위한 그릇을 개념(concept)이라고 한다. 개념을 이용하면 객체를 여러 그룹으로 분류(classification)할 수 있다.

객체란 특정한 개념을 적용할 수 있는 구체적인 사물을 의미한다. 개념이 객체에 적용됐을 때 객체를 개념의 인스턴스라고 한다.

객체의 분류 장치로서 개념을 이야기할 때 아래 세가지 관점이 있다.

  • 심볼(symbol): 개념을 가리키는 간략한 이름이나 명침
  • 내연(intension): 개념의 완전한 정의를 나타내며 내연의 의미를 이용해 객체가 개념에 속하는지 여부를 확인할 수 있다.
  • 외연(extension): 개념에 속하는 모든 객체의 집합(set)
  • 심볼(symbol): 트럼프
  • 내연(intension): 몸이 납작하고 두 손과 두발은 네모 귀퉁이에 달령 있는 등장 인물
  • 외연(extension): 정원사, 병사, 신하, ...

분류란 객체에 특정한 개념을 적용하는 작업이다. 객체에 특정한 개념을 적용하기로 결심했을 때 우리는 그 객체를 특정한 집합의 멤버로 분류하고 있는 것이다.

분류는 객체지향의 가장 중요한 개념 중 하나이다. 어떤 객체를 어떤 개념으로 분류할지가 객체지향의 품질을 결정한다.

타입은 개념과 동일하다. 따라서 타입이란 우리가 인식하고 있는 다양한 사물이나 객체에 적용할 수 있는 아이디어나 관념을 의미한다. 어떤 객체에 타입을 적용할 수 있을 때 그 객체를 타입의 인스턴스라고 한다. 타입의 인스턴스는 타입을 구성하는 외연인 객체 집합의 일원이 된다.

같은 타입에 속한 객체는 행동만 동일하면 서로 다른 데이터를 가질 수 있다. 여기서 동일한 행동이란 동일한 책임을 의미하며, 동일한 책임이란 동일한 메시지 수신을 의미한다.

객체가 어떤 행동을 하느냐에 따라 객체의 타입이 결정된다.

따라서 동일한 타입에 속한 객체는 내부의 데이터 표현 방식이 다르더라도 동일한 메시지를 수신하고 이를 처리할 수 있다. 다만 내부의 표현 방식이 다르기 때문에 동일한 메시지를 처리하는 방식은 서로 다를 수 밖에 없다. 이것은 다형성에 의미를 부여한다.
다형성이란 동일한 요청에 대해 서로 다른 방식으로 응답할 수 있는 능력을 뜻한다.

데이터의 내부 표현 방식과 무관하게 행동만이 고려 대상이라는 사실은 외부에 데이터를 감춰야 한다는 것을 의미한다. 따라서 훌륭한 객체지향 설계는 외부에 행동만을 제공하고 데이터는 행동 뒤로 감춰야 한다. 이 원칙을 흔히 캡슐화라고 한다.

행동에 따라 객체를 분류하기 위해서는 객체가 내부적으로 관리해야 하는 데이터가 아니라 객체가 외부에 제공하애 하는 행동들을 먼저 생각해야 한다. 이를 위해서는 객체가 외부에 제공해야 하는 책임을 먼저 결정하고 그 책임을 수행하는데 데이터를 나중에 결정한 후, 데이터를 책임을 수행하는데 필요한 외부 인터페이스 뒤로 캡슐화해야 한다.

일반화/특수화 관계는 좀 더 일반적인 한 타입과 좀 더 특수한 한 타입 간의 관계다. 이 때 좀 더 일반적인 타입을 슈퍼타입(Supertype)이라고 하고 좀 더 특수한 타입을 서브타입(Subtype)이라고 한다.

두 타입 간의 관계는 행동에 의해 결정된다. 일반적으로 서브타입은 슈퍼타입의 행위와 호환되기 때문에 서브타입은 슈퍼타입을 대체할 수 있어야 한다.

타입은 추상화다. 타입을 이용하면 객체의 동적인 특성을 추상화할 수 있다. 결국 타입은 시간에 따른 객체의 상태 ㅂ녀경이라는 복잡성을 단수화할 수 있는 효과적인 방법이다.

타입의 목적
그래서 타입을 왜 사용하는가? 객체의 복잡성을 추상화하기 위해서이다. 엘리스의 키가 현재 얼마인지는 중요하지 않다. 어떤 행동을 할 때 키가 변하는지가 중요하다.

4. 역할, 책임, 협력

책임

어떤 객체가 어떤 요청에 대해 대답해 줄 수 있거나, 적절한 행동을 할 의무가 있는 경우 해당 객체가 책임을 가진다고 말한다.

책임은 객체에 의해 정의되는 응집도 있는 행위의 집합으로, 객체가 알아야 하는 정보와 객체가 수행할 수 있는 행위에 대해 개략적으로 서술한 문장이다.

객체의 책임을 두 가지 범주로 나눠보자.

  • 하는 것
    • 객체를 생성하거나 계산을 하는 등의 스스로 하는 것
    • 다른 객체의 행동을 시작시키는 것
    • 다른 객체의 행동을 제어하고 조절하는 것
  • 아는 것
    • 개인적인 정보에 관해 아는 것
    • 관련된 객체에 관해 아는 것
    • 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것

역할

책임의 집합은 역할을 의미한다. 역할은 재사용 가능하고 유연한 객체지향 설계를 낳는 매우 중요한 구성요소이다. 역할은 객체지향 설계의 단순성(simplicity), 유연성(flexibility), 재사용성(reusability)를 뒷받침하는 핵심 개념이다.

역할의 가장 큰 가치는 하나의 협력 안에 여러 종류의 객체가 참여할 수 있게 함으로써 협력을 추상화할 수 있다는 것이다.

5. 책임과 메시지

자율적인 책임의 특징은 객체가 '어떻게(how)' 해야 하는가가 아니라 '무엇(what)'을 해야 하는가를 설명한다는 것이다.

메시지와 메서드

하나의 객체는 메시지를 전송함으로써 다른 객체에 접근한다. 메시지를 전송할 때 추가적인 정보가 필요한 경우 메시지의 인자(argument)를 통해 추가 정보를 제공할 수 있다.

메시지를 수신한 객체가 실행 시간에 메서드를 선택할 수 있다는 사실은 다른 프로그래밍 언어와 객체지향 프로그래밍 언어를 구분 짓는 핵심적인 특징 중 하나다. 이것은 프로시저 호출에 대한 실행 코드를 컴파일 시간에 결정하는 절차적인 언어와 확연히 구분되는 특징이다.

다형성

다형성이란 서로 다른 유형의 객체가 동일한 메시지에 대해 서로 다르게 반응하는 것을 의마한다. 좀 더 구체적으로 말해 서로 다른 타입에 속하는 객체들이 동일한 메시지를 수신할 경우 서로 다른 메서드를 이용해 메시지를 처리할 수 있는 메커니즘을 가리킨다.

송신자가 수신자에 대해 매우 적은 정보만 알고 있더라도 상호 협력이 가능하다는 사실은 설계의 품질에 큰 영향을 미친다.

  • 첫째, 협력이 유연해진다.
  • 둘째, 협력이 수행되는 방식을 확장할 수 있다.
  • 셋째, 협력이 수행되는 방식을 재사용할 수 있다.

수신자와 송신자는 메시지라는 얇은 끈으로만 이어져 있다. 메시지를 기반으로 한 두 객체 사이의 이 낮은 결합도가 바로 설계를 유연하고 확장 가능하며 재사용 가능하게 만드는 비결이다.

객체 인터페이스

일반적으로 인터페이스란 어떤 두 사물이 마주치는 경계 지점에서 서로 상호작용할 수 있게 이어주는 방법이나 정치를 의미한다.

  1. 인터페이스의 사용법만 알고 있으면 대상의 내부 구조나 동작 방법을 몰라도 상호작용이 가능하다.

  2. 인터페이스가 변경되지 않고 단순히 내부 구성이나 작동 방식이 변경되는 것은 인터페이스 사용자에게 아무런 영향도 미치지 않는다.

  3. 인터페이스가 동일하기만 하다면 어떤 대상과도 상호작용할 수 있다.

인터페이스와 구현의 분리

객체 관점에서 생각하는 방법

  • 좀 더 추상적인 인터페이스. 수신자의 자율성을 보장한다.

  • 최소 인터페이스. 외부에서 사용할 필요가 없는 인터페이스는 최대한 노출하지 마라. 객체의 내부를 수정하더라도 외부에 미치는 영향을 최소화 할 수 있다.

  • 인터페이스와 구현 간에 차이가 있다는 점을 인식.

구현
객체지향의 세계에서 내부 구조와 작동 방식을 가리키는 고유의 용어는 구현(implementation)이다. 객체를 구성하지만 공용 인터페이스에 포함되지 않는 모든 것이 구현에 포함된다.

인터페이스와 구현의 분리 원칙
훌륭한 객체란 구현을 모른 채 인터페이스만 알면 쉽게 상호작용할 수 있는 객체를 의미한다. 이것은 객체를 설계할 때 객체 외부에 노출되는 인터페이스와 객체의 내부에 숨겨지는 구현을 명확하게 분리해서 고려해야 한다는 것을 의미한다. 이를 인터페이스와 구현의 분리 원칙이라고 한다.

인터페이스와 구현의 분리 원칙이 중요한 이유는 소프트웨어는 항상 변경되기 때문이다.

인터페이스와 구현을 분리한다는 것은 변경될 만한 부분을 객체의 내부에 꽁꽁 숨겨 놓는 다는 것을 의미한다. 일반적으로 이 원칙을 수행하기 위한 객체 설계 방법을 캡슐화라고 한다.

캡슐화

객체의 자율성을 보존하기 위해 구현을 외부로부터 감추는 것을 캡슐화라고 한다. 캡슐화는 정보 은닉이라고 부르기도 한다.

6. 객체 지도

두 가지 재료: 기능과 구조

안정적인 재료: 구조

도메인 모델

사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다. 도메인 모델에서 모델이란 대상을 단순화해서 표현한 것이다. 도메인 모델이란 사용자가 프로그램을 사용하는 대상 영역에 관한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태다.

도메인 모델을 기반으로 설계하고 구현하는 것은 사용자가 도메인을 바라보는 관점을 그대로 코드에 반영할 수 있게 한다. 결과적으로 표현적 차이는 줄어들 것이며, 사용자의 멘탈 모델이 그대로 코드에 녹아 스며들게 될 것이다.

불안정한 기능을 담는 안정적인 도메인 모델

도메인 모델을 기반으로 코드를 작성하는 두 번째 이유는 도메인 모델이 제공하는 구조가 상대적으로 안정적이기 때문이다.

도메인 모델의 핵심은 사용자가 도메인을 바라보는 관점을 반영해 소프트웨어를 설계하고 구현하는 것이다. 본질적이라는 것은 변경이 적고 비교적 그 특성이 오랜 시간 유지된다는 것을 의미한다.

결론적으로 안정적인 구조를 제공하는 도메인모델을 기반으로 소프트웨어의 구조를 설계하면 변경에 유연하게 대응할 수 있는 탄력적인 소프트웨어를 만들 수 있다.

불안정한 재료: 기능

유스 케이스

사용자가 목표를 달성하기 위해 사용자와 시스템 간에 이뤄지는 상호작용의 흐름을 텍스트로 정리한 것을 유스케이스라고 한다.

안정적인 도메인 모델을 기반으로 시스템의 기능을 구현할 경우 시스템의 기능이 변경되더라도 비즈니스의 핵심 정책이나 규칙이 변경되지 않는 한 전체적인 구조가 한 번에 흔들리지는 않는다. 이것이 알반적으로 객체지향이 기능의 변경에 대해 좀 더 유연하게 대응할 수 있는 패러다임이라고 일컬어지는 이유다.

객체지향의 가장 큰 장점은 도메인을 모델링하기 위한 기법과 도메인을 프로그래밍하기 위해 사용하는 기법이 동일하다는 점이다. 따라서 도메인 모델링에서 사용한 객체와 개념을 프로그래밍 설계에서의 객체와 클래스로 매끄럽게 변환할 수 있다. 앞에서 객체지향의 이 같은 특성을 연결완전성이라고 설명했다.

7. 함께 모으기

객체 지향 설계 안에 존재하는 세 가지 상호 연관된 관점

  1. 개념 관점(Conceptual Perspective)
    도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다. 도메인이란 사용자들이 관심을 가지고 있는 특정 분야나 주제를 말하며 소프트웨어는 도메인에 존재하는 문제를 해결하기 위해 개발된다.

  2. 명세 관점(Specification Perspective)
    도메인의 관점을 벗어나 실제로 소프트웨어 안에서 살아 숨쉬는 객체들의 책임에 초점을 맞추게 된다.

  3. 구현 관점(Implementation Perspective)
    객체들이 책임을 수행하는 데 필요한 동작하는 코드를 작성하는 것이다. 따라서 프로그래머는 객체의 책임을 '어떻게' 수행할 것인가에 초점을 맞추며 인터페이스를 구현하는 데 필요한 속성과 메서드를 클래스에 추가한다.

인터페이스와 구현을 분리하라

캡슐화를 위반해서 구현을 인터페이스 밖으로 노출해서도 안 되고, 인터페이스와 구현을 명확하게 분리하지 않고 흐릿하게 섞어놓아서도 안 된다.

0개의 댓글