-
클래스는 구현 도구에 불과하다.
-
애플리케이션은 클래스로 구성되지만 메시지를 통해 정의된다.
-
객체가 수신하는 메시지들이 객체의 퍼블릭 인터페이스를 구성한다.
-
훌륭한 퍼블릭 인터페이스를 얻기 위해서는 책임 주도 설계 방법을 따르는 것만으로는 부족하다.
- 유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 익히고 적용해야 한다.
-
협력 안에서 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라고 부른다.
-
협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다.
-
객체는 협력에 참여하는 동안 클라이언트와 서버의 역할을 동시에 수행하는 것이 일반적이다.
-
**메시지(message)**는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다.
-
한 객체가 다른 객체에게 도움을 요청하는 것: 메시지 전송(message sending) 또는 메시지 패싱(message passing) 이라고 부른다.
-
메시지를 전송하는 객체를 메시지 전송자(message sender), 메시지를 수신하는 객체를 메시지 수신자(message receiver) 라고 부른다.
-
메시지는 오퍼레이션명(operation name) 과 인자(argument) 로 구성된다.
- 메시지 전송은 여기에 메시지 수신자를 추가한 것이다.
-
따라서 메시지 전송은 메시지 수신자, 오퍼레이션명, 인자의 조합이다.
-
메시지를 수신했을 때 실제로 어떤 코드가 실행되는지는 메시지 수신자의 실제 타입에 따라 달라진다.
- 다형성을 이용한 코드가 그 예시
-
메시지를 수신했을 때 실제로 수행되는 함수 또는 프로시저를 메서드 라고 부른다.
- 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다.
- 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결한다.
- 즉 컴파일 시점과 실행 시점의 의미가 달라진다.
-
메시지와 메서드의 구분은 메시지 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다.
- 메시지 전송자는 수신자가 어떤 클래스의 인스턴스인지 어떤 방식으로 요청을 처리하는지 모른다.
- 메시지 수신자는 누가 메시지를 전송하는지 모른다.
-
실행 시점에 메시지와 메서드를 바인딩함으로써 두 객체 사이의 결합도를 낮춘다.
-
외부에서는 객체가 공개하는 메시지를 통해서만 객체와 상호작용을 할 수 있다.
-
객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스 라고 부른다.
-
퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션(operation) 이라고 부른다.
- 메서드는 오퍼레이션의 여러 가능한 구현 중 하나이다.
-
오퍼레이션은 구현이 아닌 추상화이다.
-
메서드는 오퍼레이션을 구현한 것이다.
- 메시지 전송
- 오퍼레이션 호출
- 메서드 실행
- 오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합쳐 시그니처(signature) 라고 부른다.
- 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이다.
- 메서드는 시그니처에 구현을 더한 것이다.
- 메시지가 수신되면 오퍼레이션의 시그니처와 동일한 메서드가 실행된다.
-
좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야 한다.
- 최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함한다.
- 추상적인 인터페이스는 어떻게 수행하는지가 아니라 무엇을 하는지를 표현한다.
-
최소주의를 따르면서 추상적인 인터페이스를 설계할 수 잇는 가장 좋은 방법은 책임 주도 설계 방법을 따르는 것이다.
-
이와는 별개로 훌륭한 인터페이스가 가지는 공통적인 특징을 알면 좋다.
- 디미터 법칙
- 묻지 말고 시켜라
- 의도를 드러내는 인터페이스
- 명령-쿼리 분리
-
객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이다.
-
디미터 법칙(Law of Demeter): 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라.
- 낯선 자에게 말하지 말라
- 오직 인접한 이웃하고만 말하라
- 오직 하나의 도트(.)만 사용하라
-
클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 하자.
- this 객체
- 메서드의 매개변수
- this의 속성
- this의 속성인 컬렉션의 요소
- 메서드 내에서 생성된 지역 객체
screening.getMovie().getDiscountConditions();
-
위와 같은 코드를 기차 충돌(train wreck) 이라고 부른다.
-
기차 충돌은 클래스의 내부 구현이 외부로 노출됐을 때 나타나는 전형적인 형태다.
-
디미터 법칙은 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다.
-
묻지 말고 시켜라(Tell, Don't Ask)
-
메시지 전송자는 메시지 수신자의 상태를 기반으로 결정을 내린 후 수신자의 상태를 바꿔서는 안 된다.
-
절차적인 코드는 정보를 얻은 후에 결정한다. 객체지향 코드는 객체에게 그것을 하도록 시킨다.
-
내부의 상태를 묻는 오퍼레이션을 인터페이스에 포함하고 있다면 더 나은 방법은 없는지 고민해보자.
-
상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 대체하자.
-
단순히 객체에게 묻지 않고 시킨다고 해서 모든 문제가 해결되는 것은 아니다.
-
인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.
-
메서드를 명명하는 두 가지 방법
- 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름 짓기
- '어떻게'가 아니라 '무엇'을 하는지 드러내기
-
어떻게 수행하는지를 드러내는 이름은 메서드의 내부 구현을 설명하는 이름이다.
-
무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야 한다.
-
무엇을 하느냐에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있다.
-
의도를 드러내는 선택자(Intention Revealing Selector)
- 매우 다른 두 번째 구현을 상상하고 해당 메서드에 동일한 이름을 붙인다고 생각하기
- 디미터 법칙을 위반하는 설계은 인터페이스 구현의 분리 원칙 을 위반한다.
- 클라이언트에게 객체의 구현을 노출하고 있음
- 디미터 법칙과 묻지 말고 시켜라 스타일을 따르는 인터페이스를 얻었다면 인터페이스가 클라이언트의 의도를 올바르게 반영했는지 확인해야 한다.
- 오퍼레이션의 이름은 클라이언트의 의도를 표현하는 이름을 가져야 한다.
- 소프트웨어 설계에 법칙이란 존재하지 않은다.
- 법칙에는 예외가 없지만 원칙에는 예외가 넘쳐난다.
- 초보자는 원칙을 맹목적으로 추종한다.
- 적용하려는 원칙들이 서로 충돌하는 경우에도 원칙에 정당성을 부여하고 억지로 끼워 맞추려고 노력한다.
- 원칙이 현재 상황에 부적합하다고 판단된다면 과감하게 원칙을 무시하라.
- 하나 이상의 도트를 사용하는 모든 케이스가 디미터 법칙 위반은 아니다.
- 객체 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.
-
클래스는 하나의 변경 원인만을 가져야 한다.
- 서로 상관없는 책임들이 함께 뭉쳐있는 클래스는 응집도가 낮으면 작은 변경으로도 쉽게 무너질 수 있다.
-
묻지 말고 시켜라 스타일을 준수하는 퍼블릭 인터페이스만을 목표로 하다간 적절하지 못한 곳에 책임이 할당될 수 있다.
-
자료 구조라면 내부를 노출해야 하므로 디미터 법칙을 적용할 필요가 없다.
-
객체에게 시키는 것이 항상 가능하지 않기에 가끔씩은 물어야 한다.
-
명령-쿼리 분리(Command-Query Separation) 원칙: 퍼블릭 인터페이스에 오퍼레이션을 정의할 때 참고할 수 있는 지침을 제공한다.
-
루틴(routine): 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈
-
루틴은 프로시저(procedure) 와 함수(function) 으로 구분할 수 있음
- 프로시저는 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류
- 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류
-
명령(Command) 과 쿼리(Query) 는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다.
- 객체의 상태를 수정하는 오퍼레이션을 명령이라고 부른다.
- 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다.
-
명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.
-
어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다.
- 객체의 상태를 변경하려는 명령은 반환값을 가질 수 없다.
- 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.
-
질문이 답변을 수정해서는 안 된다.
- 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안 된다.
- 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다.
-
명령과 쿼리를 뒤섞으면 실행 결과를 예측하기가 어려워질 수 있다.
-
겉으로 보기에는 쿼리처럼 보이지만 내부적으로 부수효과를 가지는 메스드는 이해하기 어렵고 잘못 사용하기 쉽다.
-
명령과 쿼리를 분리하면 인터페이스가 복잡해진 것처럼 보이지만 그로써 얻는 이점이 더 크다.
-
명령과 쿼리를 분리하면 명령형 언어의 틀 안에서 참조 투명성(referential transparency) 의 장점을 제한적이나마 누릴 수 있다.
- 어떤 표현식 e가 있을 때 e의 값으로 e가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성
- 표현식 f(1)을 그 값인 3으로 바꿔도 결과가 달라지지 않는 특성
-
수학은 참조 투명성을 엄격하게 준수하는 가장 유명한 체계다.
-
부수효과(side effect)
- 컴퓨터는 x라는 값을 초기화한 후 다른 값으로 변경하는 것이 가능하다.
-
참조 투명성을 만족하는 식은 두 가지 장점을 제공한다.
- 모든 함수를 이미 알고 있는 하나의 결괏값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.
- 모든 곳에서 함수의 결괏값이 동일하기 때문에 식의 순서를 변경하더라도 각 식의 결과는 달라지지 않는다.
- 부수효과가 존재하지 않는 수학적인 함수에 기반한다.
- 참조 투명성의 장점을 극대화 할 수 있다.
- 명령형 프로그래밍에 비해 프로그램의 실행 결과를 이해하고 예측하기가 더 쉽다.
- 병렬 처리에서 강점이 있다.
-
지금까지 배운 원칙을 잘 적용하기 위해서는 메시지를 먼저 선택하고 그 후에 메시지를 처리할 객체를 선택하면 된다.
-
오퍼레이션의 시그니처는 단지 오퍼레이션의 이름과 인자의 반환값의 타입만 명시할 수 있다.
-
시그니처에는 어떤 조건이 만족돼야만 오퍼레이션을 호출할 수 있고 어떤 경우에 결과를 반환받을 수 없는지를 표현할 수 없다.
-
이를 위해서는 계약에 의한 설계 라는 개념이 있다.