도니의 iOS 프로그래밍 세상

[오브젝트 2회독] 6장 - 메시지와 인터페이스 본문

OOP

[오브젝트 2회독] 6장 - 메시지와 인터페이스

Donee 2024. 11. 19. 19:28

1. 협력과 메세지

  • 객체가 다른 객체와 협력함으로써 독립적인 객체에서 더 큰 책임을 가질 수 있다
  • 이때 협력을 위한 매개체가 메시지 이다.

메시지와 메시지 전송

  • 메시지 전송자는 클라이언트, 수신자는 서버라고 부르기도 함
  • 결국 메시지 전송자는 특정 객체의 함수를 호출하는 객체, 수신자는 특정 객체를 의미

메시지와 메서드

  • 메시지를 수신했을 때, 실제 실행되는 함수 또는 프로시저를 메서드 라고 함
  • 메시지를 전송시 컴파일 시점과 실행 시점에 따라 메서드가 다를 수 있음
  • 이를 통해 유연하고 확장 가능한 코드 작성 가능

2. 인터페이스와 설계 품질

  • 좋은 인터페이스는 최소한 + 추상적 인터페이스라는 두가지 조건을 만족시켜야 함
  • 이를 위해 책임 주도 설계를 활용한다면, 객체가 필요한 메시지만 가질 수 있게 됨

디미터 법칙

객체 내부 구조에 강한 커플링을 하지 않도록, 협력의 경로를 제한하는 것

  • 메시지 수신자의 내부 구조를 묻지 않고 원하는 동작을 수행하도록 요청함(ex. a.b.c()가 아닌, a.requestC())
  • 이와 같은 구조는 정보를 알고 있는 객체에게 책임을 주어 더욱 응집도가 높은 코드를 작성할 수 있음

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

메서드 명명의 두가지 방법

  • How(어떻게)를 강조하는 명명
  • What(무엇을)을 강조하는 명명법

How를 강조한 예시

public class PeriodCondition {
   func isSatisfiedByPeriod(screening: Screening) -> Bool {
        // 조건 확인 로직
    }
}

public class SequenceCondition {
    func isSatisfiedBySequence(screening: Screening) -> Bool {
        // 조건 확인 로직
    }
}

What을 강조한 예시

public class PeriodCondition {
   func isSatisfiedBy(screening: Screening) -> Bool {
        // 조건 확인 로직
    }
}

public class SequenceCondition {
    func isSatisfiedBy(screening: Screening) -> Bool {
        // 조건 확인 로직
    }
}
  • What이 How보다 더욱 좋은 명명법
    • How는 내부 구현에 초점을 두는 명명법, 외부에서는 해당 메서드의 구현 방식을 알 필요가 없음
    • What에 집중한다면 해당 객체가 지닌 책임을 고려해서 이름을 지을 수 밖에 없음
    • 결국 외부에서 객체를 사용할 때 해당 메시지를 통해서 해당 객체에 책임을 명확히 알고 사용할 수 있게 됨
  • What을 강조하여 두 Condition class를 하나의 타입 계층으로 묶음으로써, 더욱 유연하게 설계가 가능해짐
  • 이를 켄트백은 의도를 드러내는 인터페이스라고 부르며, 더욱 잘하기 위해선 두가지 클래스를 매우 다른 방식으로 구현하여 추상적인 이름으로 한데 묶는 방식을 소개하고 있음

3. 원칙의 함정

  • 디미터 법칙등 앞서 말한 설계 원칙들이 훌륭하지만 절대적이지 않음
  • 이를통한 설계는 트레이드 오프의 산물이며, 현재 상황에 부적합할 때는 무시하는 것이 필요함
  • 원칙보다 중요한 건 해당 원칙들이 언제 유용하고 언제 유용하지 않은 지 판단하는 것이다.

디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다

IntStream.of(1, 15, 20, 3, 9)
         .filter(x -> x > 10)
         .distinct()
         .count();
  • 위 코드는 굉장히 많은 도트를 사용하지만 위 함수들은 IntStream 인스턴스를 변환하는 것
  • 따라서, 디미터 법칙을 위반하지 않음
  • 디미터 법칙의 본질은 결합도와 관련된 것, 결과적으로 하나의 인스턴스와 관련되어 있다면 결합도가 있다고 볼 수 없음

결합도와 응집도의 충돌

1. a.b.c()

2. a.requestC()
  • 1번과 2번중 어떤게 더 나은 방식일까?
  • 1번은 a클래스 내부 구조를 알고, c함수를 호출한 케이스
  • 2번은 결합도를 위해 a클래스 내부 구조를 아는 대신, a클래스에 위임 함수를 추가한 케이스
  • 2번에서의 단점은 위임 메서드를 추가함으로써, a와 관련 없는 동작이 추가된 케이스. 이때 응집도가 떨어질 수 있다
    • a에서 c라는 위임 메서드를 추가함으로써, 변경했을 때 영향도가 발생함

→ 2번이 반드시 좋지 않은건 아니지만, 이러한 단점이 있을수 있기에 고려해서 설계 해야함

4. 명령-쿼리 분리 원칙

명령

  • 객체의 상태를 변경할 수 있지만, 값을 반환할 수 없음

쿼리

  • 값을 반환할 수 있지만 객체의 상태를 변경할 수 없다(no side-effect)
  • 쿼리 내부에서 값을 변경하거나, 명령이 값을 반환할 경우 side-effect를 동료 개발자는 side-effect를 생각하지 못할 수 있음(HTTP get 함수를 통해서 DB상태가 변경된다고 생각할 수 있는가?)

결론

  • 독립적인 객체가 더 큰 작업을 하기 위해선, 협력이 필요하고 이를 위한 매개체는 메시지 이다.
  • 메시지 는 컴파일 타임과 실행 시점에 메서드 가 다르기 때문에 더욱 유연한 설계가 가능하다
  • 인터페이스의 좋은 품질을 위해선 최소한 + 추상적 인터페이스를 만족시켜야 하고, 이를 위해선 책임 주도 설계가 필요하다
  • 디미터 법칙을 통해 객체의 협력 경로를 제한하여 결합도를 낮출 수 있다.
    • 이때, 중요한 건 “.”을 제한하는게 아니고, 객체의 결합도를 제한하는 게 중요하다.
  • 의도가 드러나는 What을 강조한 명명법을 통해 더욱 유연한 설계가 가능하다.
    • How를 강조한 명명법은 내부 구현에 초점을 두어, 유연한 설계를 해친다.
  • 앞서 말한 원칙들은 모두 trade-off가 존재하기에, 유용할때와 하지 않을 때를 구분해서 써야한다.(절대 원칙X)
  • 조회를 위해 사용하는 메서드는, 객체의 상태를 변경시켜선 안된다.(side-effect를 고려하지 않고 사용하는 함수이기 때문)
Comments