우당탕탕 우리네 개발생활

[oop] 7장. 함께 모으기 (+ 부록) 본문

tech

[oop] 7장. 함께 모으기 (+ 부록)

미스터카멜레온 2024. 5. 12. 22:55
이 글은 조영호님의 <객체지향의 사실과 오해> 책을 통해 공부한 내용을 정리하기 위해 작성하였습니다. 제 개인적인 각색과 의견이 첨가되어 있어 실제 책의 내용과는 차이가 있을 수 있습니다.

 

개념 관점(Conceptual Perspective)에서 설계는 도메인 안에 존재하는 개념과 개념들 사이의 관계를 표현한다. 실제 도메인의 규칙과 제약을 최대한 유사하게 반영하는 것이 핵심이다.

명세 관점(Specifiction Perspective)에 이르면 사용자의 영역인 도메인을 벗어나 개발자의 영역인 소프트웨어로 초점이 옮겨진다. 명세 관점에서 프로그래머는 객체가 협력을 위해 '무엇'을 할 수 있는가에 초점을 맞춘다.

객체지향 설계 분야의 오래된 격언인 "구현이 아니라 인터페이스에 대해 프로그래밍하라"를 따르는 것은 명세 관점과 구현 관점을 명확하게 분리하는 것에서 부터 시작된다.

구현 관점(Implementation Perspective)은 프로그래머인 우리에게 가장 익숙한 관점으로, 실제 작업을 수행하는 코드와 연관돼 있다.

 

클래스가 은유하는 개념은 도메인 관점을 반영한다. 클래스의 공용 인터페이스는 명세 관점을 반영한다. 클래스의 속성과 메서드는 구현 관점을 반영한다.

커피 전문점 도메인

커피 전문점이라는 세상

인간의 두뇌는 세상을 이해하기 위해 객체를 직접적으로 다룰 수 있을 만큼 효율적이지 못하다. 우리가 할 수 있는 일은 동적인 객체를 정적인 타입으로 추상화해서 복잡성을 낮추는 것이다. 타입은 분류를 위해 사용된다는 것을 기억하라. 상태와 무관하게 동일하게 행동할 수 있는 객체들은 동일한 타입의 인스턴스로 분류할 수 있다.

 

참고) 실제로 도메인 모델을 작성하는 단계에서 어떤 관계가 포함 관계이고 어떤 관계가 연관 관계인지는 중요하지 않다. 초점은 어떤 타입이 도메인을 구성하느냐와 타입들 사이에 어떤 관계가 존재하는지를 파악함으로써 도메인을 이해하는 것이다. 여기서는 설명을 위해 포함 관계와 연관 관계를 구분하고 있지만 실제로는 메뉴판과 메뉴 항목 사이, 손님과 메뉴판 사이에 관계가 존재한다는 사실을 이해하는 것만으로도 충분하다.

설계하고 구현하기

커피를 주문하기 위한 협력 찾기

객체지향의 설계의 첫 번째 목표는 훌륭한 객체를 설계하는 것이 아니라 훌륭한 협력을 설계하는 것이라는 점을 잊지 말자.

협력을 설계할 때는 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 한다. 이 말은 메시지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택해야 한다는 것을 의미한다.

(대부분 다이어그램 그림 자료를 이용하여 설명하고 있기 때문에 이 챕터는 확실히 책을 통해 복습하는 것이 효과적이다.)

인터페이스 정리하기

우리가 힘들여 얻어낸 것은 객체들의 인터페이스다. 객체가 수신한 메시지가 객체의 인터페이스를 결정한다는 사실을 기억하라. 메시지가 객체를 선택했고, 선택된 객체는 메시지를 자신의 인터페이스로 받아들인다.

각 객체를 협력이라는 문맥에서 떼어내어 수신 가능한 메시지만 추려내면 객체의 인터페이스가 된다.

객체의 타입을 구현하는 일반적인 방법은 클래스를 이용하는 것이다.

참고) 설계를 간단히 끝내고 최대한 빨리 구현에 돌입하라. 머릿속에 객체의 협력 구조가 번뜩인다면 그대로 코드를 구현하기 시작하라.

코드와 세 가지 관점

코드는 세 가지 관점을 모두 제공해야 한다

(앞의 정리에서 많이 생략했지만 Customer, Menu, MenuItem, Barista, Coffee 클래스에 대한 적당한 코드 예시가 있었다. 책을 통해 확인하자)

개념 관점에서 코드를 바라보면 Customer, Menu, MenuItem, Barista, Coffee 클래스가 보인다.

명세 관점은 클래스의 인터페이스를 바라본다. 클래스의 public 메서드는 다른 클래스가 협력할 수 있는 공용 인터페이스를 드러낸다. 객체의 인터페이스는 수정하기 어렵다는 사실을 명심하라.

구현 관점은 클래스의 내부 구현을 바라본다.

다른 사람이 여러분의 코드를 읽으면서 세 가지 관점을 쉽게 포착하지 못한다면 세 가지 관점이 명확하게 드러날 수 있게 코드를 개선하라.

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

중요한 것은 여러분이 클래스를 봤을 때 클래스를 명세 관점과 구현 관점으로 나눠볼 수 있어야 한다는 것이다. 캡슐화를 위반해서 구현을 인터페이스 밖으로 노출해서도 안 되고, 인터페이스와 구현을 명확하게 분리하지 않고 흐릿하게 섞어놓아서도 안 된다. 결국 세 가지 관점 모두에서 클래스를 바라볼 수 있으려면 훌륭한 설계가 뒷받침돼야 하는 것이다.


부록 A

클래스

클래스 기반의 객체지향 언어는 아리스토텔레스의 철학을 기반으로 한다. 클래스는 객체가 공유하는 본질적인 속성을 정의한다. 대부분의 객체지향 프로그래밍 언어에서 동일한 범주에 속하는 객체는 동일한 클래스의 인스턴스여야 한다. 대부분의 객체지향 언어는 본질적인 속성은 표현할 수 있지만 우연적인 속성은 표현할 수 없다. 따라서 동일한 범주에 속하는 객체는 모두 동일한 속성을 가져야만 한다.

서브타입

어떤 범주에 속하는 다른 객체가 특정 속성을 가지고 있음을 알게 되면 그 범주와 하위 범주에 속하는 다른 객체도 그 속성을 가지고 있을 것이라고 추론할 수 있다.

크레이그 라만은 어떤 타입이 다른 타입의 서브타입이 되기 위해서는 '100% 규칙'과 'Is-a 규칙'을 준수해야 한다고 말한다.

  • 100% 규칙: 슈퍼타입의 정의가 100% 서브타입에 적용돼야만 한다. 서브타입은 속성과 연관관계 면에서 슈퍼타입과 100% 일치해야 한다.
  • Is-a 규칙: 서브타입의 모든 인스턴스는 슈퍼타입 집합에 포함돼야 한다. 이는 대개 영어로 서브타입은 슈퍼타입이다(subtype is a supertype)라는 구문을 만듦으로써 테스트할 수 있다.

상속

안타깝게도 모든 상속 관계가 일반화 관계인 것은 아니다. 일반화의 원칙은 한 타입이 다른 타입의 서브타입이 되기 위해서는 슈퍼타입에 순응(conformance)해야 한다는 것이다. 순응에는 구조적인 순응(structural conformance)과 행위적인 순응(behavioral conformance)의 두 가지 종류가 있다.

구조적인 순응은 타입의 내연과 관련된 100% 규칙을 의미한다.

행위적인 순응은 타입의 행위에 관한 것이며, 서브타입은 슈퍼타입을 행위적으로 대체 가능해야 한다. 이를 흔히 리스코프 치환 원칙(Liskov Substitution)이라고 한다.

 

여러 클래스로 구성된 상속 계층에서 수신된 메시지를 이해하는 기본적인 방법은 클래스 간의 위임(delegation)을 사용하는 것이다. 어떤 객체의 클래스가 수신된 메시지를 이해할 수 없다면 메시지를 클래스의 부모 클래스로 위임한다. 만약 부모 클래스도 메시지를 이해할 수 없다면 자신의 부모 클래스로 다시 메시지를 위임한다.