it 책/오브젝트: 코드로 이해하는 객체지향 설계

오브젝트: 코드로 이해하는 객체지향 설계 6장을 읽으며

lgvv 2024. 11. 12. 00:06

오브젝트: 코드로 이해하는 객체지향 설계 6장을 읽으며

 

느낀점

함수형 프로그래밍의 개념과 부수효과를 최소화하는 방법, 명령(Command)과 쿼리(Query)를 분리하는 설계 원칙에 대해 배운 것이 인상적이었음. 메시지를 통해 객체 간의 협력이 이루어질 때, 불필요한 부수효과를 피하는 것이 얼마나 중요한지 체감했고, 코드의 안정성과 일관성을 높이는 방법을 구체적으로 알게 되었음.

 

메시지와 인터페이스

객체지향 프로그래밍에 대한 가장 흔한 오해는 애플리케이션이 클래스의 집합으로 구성된다고 생각하는 것. 클래스는 구현 도구일 뿐.

  • 가장 중요한 재료는 클래스가 아니라 객체들이 주고받는 메시지

 

클라이언트 - 서버 모델

두 객체 사이의 협력 관계를 설명하기 위해 사용하는 전통적인 메타포는 클라이언트-서버 모델

  • 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지라는 것


메시지와 메시지 전송

용어에 대한 정리

  • 메시지: 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단
  • 메시지 전송: 다른 객체에게 도움을 요청하는 것
  • message sender: 메시지를 보내는 객체
  • message receiver: 메시지를 수신하는 객체
  • 메시지는 오퍼레이션 이름과 인자(argument)로 구성하며 메시지 전송은 여기에 메시지 수신자를 추가
// objc
[수신사 오퍼레이션명 인자]
[condition isSatisfiedBy: screening]

 

메시지와 메서드

메시지를 수신했을 때 실제로 어떤 코드가 실행되는지는 메시지 수신자의 실제 타입이 무엇인가에 달려있음.

  • 함수 또는 프로시저를 메서드라고 부름
  • 메시지와 메서드의 구분은 메시지 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 함.


퍼블릭 인터페이스와 오퍼레이션

객체는 안과 밖을 구분하는 뚜렷한 경계를 가짐

  • 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 함.
  • 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 함.
  • 단순히 메시지와 관련된 시그니처를 가리키는 경우가 대부분


시그니처

오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합쳐 시그니처라고 부름.

  • 일반적으로 메시지를 수신하면 오퍼레이션의 시그니처와 동일한 메서드 실행
  • 다형성을 사용하기 위해서 하나의 오퍼레이션에 대해 다양한 메서드를 구현

 

인터페이스와 설계 품질

최소한의 인터페이스 추상적인 인터페이스라는 조건을 만족해야 함.

  • 최소한의 인터페이스는 꼭 필요한 오퍼레이션을 인터페이스에 포함
  • 추상적인 인터페이스는 어떻게 수행하는지가 아니라 무엇을 하는지 표현
  • 객체가 메시지를 선택하는게 아닌 메시지가 객체를 선택하게 함.


디미터 법칙

협력하는 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙

  • 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한
  • 자바나 C#과 같이 도트(.)를 활용해 메시지 전송을 표현하고 즉 하나의 도트만 사용하라
  • 디미터 법칙을 따르면 부끄럼타는 코드(shy code)
    • 불필요한 어떤 것도 다른 객체에게 보여주지 않음.
  • 디미터 법칙은 협력과 구현이라는 사뭇 달라 보이는 두 가지 문맥을 유기적인 개념으로 통합.
/// ❌ 디미터 원칙 위배
screening.getMovie().getDiscountCondition()

 

위 같은 코드를 기차 충돌(train wreck)라고 부르고 외부로 노출됐을 때 나타나는 전형적인 형태

/// ✅ 원하는 것이 무엇인지를 명시하고 단순히 수행하도록
screening.calculateFee(audienceCount)

 

 

묻지 말고 시켜라

휼룡한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조

  • 다른 객체의 내부 상태에 대해서 묻지 말고 원하는 것을 명령


의도를 드러내는 인터페이스

켄트 백은 메서드를 명명하는 두 가지 방법을 설명

  • 메서드가 어떻게 수행하는지를 나타내도록 이름을 짓기
  • 어떻게가 아니라 무엇을 하는지를 드러내는 것
  • 어떻게 하느냐가 아니라 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자(Intention Revealing Seletor)라고 부른다.


함께 모으기

위의 규칙들을 잘 이해할 수 있는 가장 좋은 방법은 이런 원칙을 위배하는 코드를 많이 보는 것

  • 근본적으로 디미터 법칙을 위반하는 설계는 인터페이스 구현의 분리 원칙을 위배
  • 인터페이스에 의도를 드러내자


원칙의 함정

위의 규칙을 지키는 것은 절대적인 원칙은 아님

  • 설계를 적절하게 트레이드오프 할 수 있는 능력이 숙련자와 초보자를 구분하는 가장 중요한 능력
  • 디미터 법칙은 하나의 도트를(.)를 강제하는 규칙이 아님
    • 디미터 법칙은 하나의 도트만을 사용하는 것이 위배하는건 아니고, 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 디미터 법칙을 준수하는 것
  • 결합도와 응집도의 충돌
    • 객체에 상태를 물어본 후 반환된 상태를 기반으로 결정을 내리고 그 결정에 따라 객체의 상태를 변경하는 코드는 스타일로 변경

 

명령-쿼리 분리 원칙

필요에 따라 물어야 한다는 사실에 납득했다면 해당 원칙을 알아두면 좋음.

  • 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을 루틴(routine)이라고 부름
  • 루틴은 다시 프로시저 함수 구분할 수 있음.
  • 프로시저와 함수의 부수효과와 반환값의 유무라는 측명에서 명확하게 구분
    • 프로시저: 부수효과 발생시킬 수 있지만 값 반환할 수 없음
    • 함수: 값 반환할 수 있지만 부수효과 발생시킬 수 없음
  • 명령과 쿼리는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름
    • 명령: 객체의 상태를 수정하는 오퍼레이션
    • 쿼리: 객체와 관련된 정보를 반환
  • 개념적으로는 명령은 프로시저와 동일하고 쿼리는 함수와 동일
  • 명령과 쿼리의 두가지 역할을 수행하게 하지 말것, 명령과 쿼리를 명확하게 분리하는 것

 

명령-쿼리 분리와 참조 투명성

명령과 쿼리를 엄격하게 분류하면 객체의 부수효과를 제어하기 수월해짐

  • 쿼리의 장점은 명령이 호출되지 않는 한 순서와 횟수에 상관없이 호출될 수 있음
  • 참조 투명성의 장점을 제한적이나마 누릴 수 있게 됨.
  • side effect: 컴퓨터의 세계와 수학의 세계를 나누는 가장 큰 특징
  • 객체지향 패러다임이 객체의 상태 변경이라는 부수효과
  • 명령-쿼리 분리 원칙을 사용

 

명령형 프로그래밍

부수효과를 기반으로 하는 프로그래밍 방식

  • 상태를 변경시키는 연산들을 적절한 순서대로 나열함으로써 프로그램 작성

 

함수형 프로그래밍

부수효과가 존재하지 않는 수학적인 함수에 기반

  • 참조 투명성의 장점을 극대화할 수 있으며 명령형 프로그래밍에 비해 프로그램의 실행 결과를 이해하고 예측기가 쉽다

 

책임에 초점을 맞춰라

메시지를 먼저 선택하고 그 후에 메시지를 처리할 객체를 선택하는 것

  • 휼룡한 메시지를 얻기 위한 추발점은 책임 주도 설계 원칙을 따르는 것.
  • 책 주도 설계 방법에 따라 메시지가 객체를 결정하게 함, 설계가 아름답고 깔끔해지며 심지어 우아해지는 사실에 실감
  • 부수효과를 캡슐화하고 높은 응집도와 낮은 결합도를 가진 인터페이스를 만들 수 있는 지침을 제공
  • 오퍼레이션의 시그니처는 단지 오퍼레이션의 이름과 인자와 반환값의 타입만 명시할 수 있음.
  • 하지만 두 객체가 보장해야 하는 실행 시점의 제약 인터페이스에 명시할 수 있는 방법이 존재하지 않음
    • 이를 해결하기 위해 제안된 계약에 의한 설계 개념