용어 정리

인터페이스와 설계 품질
- 좋은 인터페이스는
최소한의 인터페이스
와추상적인 인터페이스
라는 조건을 만족해야 한다
- 최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함하고, 추상적인 인터페이스는 어떻게 수행하는지가 아니라 무엇을 하는지를 표현한다.
- 퍼블릭 인터페이스의 품질에 영향을 미치는 원칙과 기법
- 디미터 법칙 : 객체의 내부 구조를 묻지 말고 그냥 시켜라
- 묻지 말고 시켜라 : 상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 대체함으로써 인터페이스를 향상 시켜라
- 의도를 드러내는 인터페이스
- 명령-쿼리 분리
디미터 법칙
- 협력하는 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이
디미터 법칙
(내부 구조에 대한 결합을 없앰으로써 내부 구현 변경의 자유로움을 제공해줌)
디미터 법칙과 캡슐화
디미터 법칙은 캡슐화를 다른 관점에서 표현한 것이다. 디미터 법칙이 가치있는 이유는 클래스를 캡슐화하기 위해 따라야 하는 구체적인 지침을 제공하기 때문이다. 캡슐화 원칙이 클래스 내부의 구현을 감춰야 한다는 사실을 강조한다면 디미터 법칙은 협력하는 클래스의 캡슐화를 지키기 위해 접근해야 하는 요소를 제한한다.
- 디미터 법칙을 위반하는 코드의 전형적인 모습 ⇒ 기차 충돌. 여러 대의 기차가 한 줄로 늘어서 충돌한 것처럼 보이기에
screening.getMovie().getDiscountConditions(); // 디미터 법칙 위반 screening.calculateFee(audienceCount); // 디미터 법칠 따르도록 코드 개선
- 일반적으로 프로그램에 노출되는 객체 사이의 관계가 많아질수록 결합도가 높아지기 때문에 프로그램은 불안정해진다.
묻지말고 시켜라
- 디미터 법칙은 객체의 내부 구조를 묻는 메시지가 아니라 수신자에게 무엇을 시키는 메시지가 더 좋은 메시지라고 속삭인다.
묻지 말고 시켜라
절차적인 코드는 정보를 얻은 후에 결정한다. 객체지향 코드는 객체에게 그것을 하도록 시킨다.
- 내부의 상태를 이용해 어떤 결정을 내리는 로직이 객체 외부에 존재하는가? 그렇다면 해당 객체가 책임져야 하는 어떤 행동이 객체 외부로 누수된 것이다.
의도를 드러내는 인터페이스
- 메서드의 이름을 메서드가 작업을
어떻게(x)
수행하는지(구현)를 나타내도록 이름 짓지 말고무슨
작업
을 수행하는지를 나타내도록 지어라
public class PeriodCondition { pubilc boolean isSatisfiedByPeriod(Screening screening) { ... } } public class SequenceCondition { public boolean isSatisfiedBySequence(Screening screening) { ... } }
- 메서드가 어떻게 수행하느냐가 아니라 무엇을 하느냐(
의도를 드러내는 선택자
-Intention Revealing Selector
)에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있는 가능성이 커진다.
하나의 구현을 가진 메시지의 이름을 일반화하도록 도와주는 간단한 훈련 방법을 소개하겠다. 매우 다른 두번째 구현을 상상하라. 그러고는 해당 메서드에 동일한 이름을 붙인다고 상상해보라. 그렇게 하면 아마도 그 순간에 여러분이 할 수 있는 한 가장 추상적인 이름을 메서드에 붙일 것이다. — Kent Beck
- 수행 방법에 관해서는 언급하지 말고 결과와 목적만을 포함하도록 클래스와 오퍼레이션의 이름을 부여하라. 이렇게 하면 클라이언트 개발자가 내부를 이해해야 할 필요성이 줄어든다.
- 오퍼레이션의 이름은 협력이라는 문맥을 반영해야 한다.
원칙의 함정
- 잊지 말아야 하는 사실은 설계가 트레이드오프의 산물이라는 것. 설계를 적절하게 트레이드오프 할 수 있는 능력이 숙련자와 초보자를 구분하는 가장 중요한 기준이라고 할 수 있음. 초보자는 원칙을 맹목적으로 추종함
- 원칙이 현재 상황에 부적합하다고 판단된다면 과감하게 원칙을 무시하라. 원칙을 아는 것보다 더 중요한 것은 언제 원칙이 유용하고 언제 유용하지 않은지를 판단할 수 있는 능력을 기르는 것이다.
결합도와 응집도의 충돌
- 안타깝게도 묻지말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되는 것은 아니다.
- 모든 상황에서 맹목적으로 위임 메서드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션들이 공존하게 된다.
public class PeriodCondition implements DiscountCondition { public boolean isSatisfiedBy(Screening screening) { return screening.getStartTime().getDayofWeek().equals(dayOfWeek) & ... } }
- 위의 상황에서 Screening의 내부 구현을 PeriodCondition에서 다 안다고 해서 캡슐화를 위반한 것으로 보일 수 있음.
- 그렇다고 해당 구현을 Screening으로 옮겨버리면 묻지 말고 시켜라 스타일을 준수하는 퍼블릭 인터페이스를 얻을 수 있다고 생각하지만, 이렇게 되면 Screening이 기간에 따른 할인 조건을 판단하는 책임을 떠안게 된다. 이것이 Screening이 담당해야 하는 본질적인 책임은 아니다. ⇒ 응집도가 낮아지게 됨
- 또한, Screening이 PeriodCondition의 인스턴스 변수를 인자로 받기 때문에 PeriodCondition의 인스턴스 변수 목록이 변경될 경우에도 영향을 받게 됨. Screening과 PeriodCondition의 결합도를 높임.
- 따라서, Screening의 캡슐화를 향상시키는 것보다 Screening의 응집도를 높이고 Screening과 PeriodCondition 사이의 결합도를 낮추는 것이 전체적인 관점에서 더 좋은 방법이다. !!
- 객체에게 시키는 것이 항상 가능한 것은 아니다. 가끔씩은 물어야 한다. 여기서 강조하고 싶은 것은 소프트웨어 설계에 법칙이란 존재하지 않는다는 것이다. 원칙을 맹신하지마라. 원칙이 적절한 상황과 부적절한 상황을 판단할 수 있는 안목을 길러라. 설계는 트레이드 오프의 산물이다.
명령-쿼리 분리 원칙
용어 정리
- 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을
루틴
이라고 부름
- 루틴은 다시
프로시저
와함수
로 구분할 수 있다. 프로시저와 함수를 같은 의미로 혼용하는 경우가 많지만 사실 프로시저와 함수는 부수효과와 반환값의 유무라는 측면에서 명확하게 구분됨. 프로시저
: 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류함수
: 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류
- 명령과 쿼리는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다.
명령
: 객체의 상태를 수정하는 오퍼레이션쿼리
: 객체와 관련된 정보를 반환하는 오퍼레이션- 개념적으로 명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.
- 명령 - 쿼리 분리 원칙의 요지는 오퍼레이션은 부수효과를 발생시키는 명령이거나, 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다는 것이다. 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안 된다.
- 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
- 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.
- 명령과 쿼리를 뒤섞으면 실행 결과를 예측하기가 어려워질 수 있다. isSatisfied 메서드처럼 겉으로 보기에는 쿼리처럼 보이지만 내부적으로 부수효과를 가지는 메서드는 이해하기 어렵고, 잘못 사용하기 쉬우며, 버그를 양산하는 경향이 있다. 가장 깔끔한 해결책은 명령과 쿼리를 명확하게 분리하는 것이다.
책임에 초점을 맞춰라
- 디미터 법칙을 준수하고 묻지 말고 시켜라 스타일을 따르면서도 의도를 드러내는 인터페이스를 설계하는 아주 쉬운 방법이 있다. 메시지를 먼저 선택하고 그 후에 메시지를 처리할 객체를 선택하는 것이다.
- 훌륭한 메시지를 얻기 위한 출발점은 책임 주도 설계 원칙을 따르는 것이다. 책임 주도 설계에서는 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하기 때문에 협력에 적합한 메시지를 결정할 수 있는 확률이 높아진다.