Table of contents
Open Table of contents
개요
- 객체는 협력을 위해 존재한다.
- 협력은 객체가 존재하는 이유와 문맥을 제공한다.
- 가능하면 유사한 기능을 구현하기 위해 유사한 협력 패턴을 사용해야 한다.
핸드폰 과금 시스템 변경하기
핸드폰 과금 시스템의 요금 정책을 확장한다.
유형 | 형식 | 예 |
---|---|---|
고정요금 방식 | A초당 B원 | 10초당 18원 |
시간대별 방식 | A시부터 B시까지 C초당 D원 B시부터 C시까지 C초당 E원 | 00시부터 19시까지 10초당 18원 19시부터 24시까지 10초당 15원 |
요일별 방식 | 평일에는 A초당 B원 공휴일에는 A초당 C원 | 평일에는 10초당 38원 공휴일에는 10초당 19원 |
구간별 방식 | 초기 A분 동안 B초당 C원 A분 ~ D분까지 B초당 D원 D분 초과 시 B초당 E원 | 초기 1분 동안 10초당 50원 초기 1분 이후 10초당 20원 |
- 고정요금 방식 : 일정 시간 단위로 동일한 요금을 부과하는 방식이다. 모든 통화에 대해 동일하게 10초당 9원을 부과하는 방식이 고정요금 방식의 예에 해당한다. 기존의 ‘일반 요금재와 동일하다.
- 시간대별 방식 : 하루 24시간을 특정한 시간 구간으로 L~ 후 각 구간별로 서로 다른 요금을 부과하는 방식이다. 예를 들어 , 0시 ~ 19시까자는 10초당 18원을, 19시부터 24시까지는 10초당 15원의 요금을 부과하는 방식이다. 기존의 ‘심야 할인 요금제’는 밤 10시를 가준으로 요금을 부과한 시간대별 방식이다.
- 요일별 방식 : 요일별로 요금을 차등 부과하는 방식이다 이 방식을 사용하면 월요일부터 금요일까지는 10초당 38 원을, 토요일과 일요일에는 10초당 19원을 부과하는 요금제를 만둘 수 있다.
- 구간별 방식 : 전체 통화 시간을 일정한 통화 시간에 따라 나누고 각 구간별로 요금을 차등 부과하는 방식이다. 예를 들어, 통화 구간을 초기 1분과 1분 이후로 나눈 후 초기 1분 동안은 10초당 50원을, 그 이후에는 10초당 20원을 부과하는 방식이 구간별 방식에 해당한다. 만약 어떤 사용자의 전체 통화 시간이 60분이라면 처음 1분에 대해서는 10초당 50원이 부과되고 나머지 59분에 대해서는 10초당 20원의 요금이 부과될 것이다.
고정요금 방식 구현하기
기존의 일반요금제와 동일하기 때문에 RegularPolicy
클래스의 이름을 FixedFeePolicy
로 수정하기만 하면 된다.
시간대별 방식 구현하기
- 통화 기간을 일자별로 분리한다.
- 일자별로 분리된 기간을 다시 시간대별 규칙에 따라 분리한 후 각 기간에 대해 요금을 계산한다.
4개의 서로 다른 List를 가지는 방법으로 같은 규칙에 포함된 요소들은 List안에서 동일한 인덱스에 위치하는 방법으로 해결하였다.
public class TimeOfDayDiscountPolicy extends BasicRatePolicy {
private List<LocalTime> starts = new Arraylist<>();
private List<LocalTime> ends = new ArrayList<>();
private List<Duration> durations = new ArrayList();
private List<Money> amounts = new ArrayList<>();
};
요일별 방식 구현하기
각 규칙은 요일의 목록, 단위 시간, 단위 요금이라는 세 가지 요소로 구성된다. 요일별 방식을 사용하면 월요일부터 금요일까지는 10초가 38원을, 토요일과 일요일에는 10초당 19원을 부과하는 식으로 요금 정책을 설정할 수 있다.
요일별 방식 역시 통화 기간이 여러 날에 걸쳐있을 수 있기 때문에 시간대별 방식과 동일하게 통화 기간을 날짜 경계로 분리하고 분리된 각 통화 기간을 요일별로 설정된 요금 정책에 따라 적절하게 계산해야 한다.
구간별 방식 구현하기
지금까지 구현한 방식 클래스를 천천히 살펴보자
구현한 세 클래스는 통화 요금을 정확하게 계산하고 있고 응집도와 결합도 측면에서도 특별히 문제는 없어 보인다.
하지만 이 클래스들을 함께 모아놓고 보면 그동안 보이지 않던 문제점이 보이기 시작한다.
현재 구현의 가장 큰 문제점은 유사한 문제를 해결하고 있음에도 불구하고 설계에 일관성이 없다는 것이다. 이 클래스들은 기본 정책을 구현한다는 공통의 목적을 공유한다. 하지만 정책을 구현하는 방식은 완전히 다르다. 개념적으로 연관돼 있지만 구현 방식에 있어서는 완전히 제각각이라는 것이다.
비일관성은 두 가지 상황에서 발목을 잡는다.
- 새로운 구현을 추가해야 하는 상황 : 어떤 방식으로 새로운 구현을 할지 결정하기 어렵게 된다. 추가될수록 일관성은 점점 더 어긋나게 된다.
- 기존의 구현을 이해해야 하는 상황 : 구현 방식이 다르기 때문에 요일별 방식의 구현을 이해해도 시간대별 방식을 이해하기 쉽지 않다.
유사한 기능은 유사한 방식으로 구현해야 한다.
설계에 일관성 부여하기
협력을 일관성 있게 만들기 위한 기본 지침
- 변하는 개념을 변하지 않는 개념으로부터 분리하라.
- 변하는 개념을 캡슐화하라.
애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킨다. 바뀌는 부분을 따로 뽑아서 캡슐화한다. 그렇게 하면 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있다.
캡슐화 다시 살펴보기
많은 사람들은 객체의 캡슐화에 관한 이야기를 들으면 반사적으로 데이터 은닉 을 떠올린다.
그러나 캡슐화는 데이터 은닉 이상이다.
GOF의 조언에 따르면 캡슐화란 단순히 데이터를 감추는 것이 아니다. 소프트웨어 안에서 변할 수 있는 모든 개념을 감추는 것이다.
다음과 같은 다양한 종류의 캡슐화가 존재한다.
- 데이터 캡슐화: 클래스는 내부에서 관리하는 데이터를 캡슐화한다.
- 메서드 캡슐화: 클래스 내부의 행동을 캡슐화 한다.
- 객체 캡슐화: 객체와 객체 사이의 관계를 캡슐화 한다. => 객체 캡슐화는 합성을 의미한다.
- 서브타입 캡슐화: 서브타입의 종류를 캡슐화 한다. => 다형성의 기반이 된다.
서브타입 캡슐화와 객체 캡슐화를 적용하는 방법은 다음과 같다.
- 변하는 부분은 분리해서 타입 계층을 만든다.
- 변하지 않는 부분의 일부로 타입 계층을 합성한다.
일관성 있는 기본 정책 구현하기
변경 분리하기
일관성 있는 협력을 만들기 위한 첫 번째 단계는 변하는 개념과 변하지 않는 개념을 분리하는 것이다.
- 기본 정책은 한 개 이상의 ‘규칙’으로 구성된다.
- 하나의 ‘규칙’은 ‘적용조건’과 ‘단위요금’의 조합이다.
변경 캡슐화하기
-> 규칙을 구현 FeeRule
, 적용조건을 구현 FeeCondition
협력 패턴 설계하기
협력은 BasicRatePolicy
가 calculateFee
메시지를 수신했을 때 시작된다. BasicRatePolicy
의 calculateFee
메서드는 인자로 전달받은 통화목록 (List〈Call〉
타입)의 전체 요금을 계산한다.
BasicRatePolicy
는 목록에 포함된 각 Call별로 FeeRule
의 calculateFee
메서드를 실행한다. 하나의 BasicRatePolicy
는 하나 이상의 FeeRule
로 구성되기 때문에 Call 하나당 FeeRule
에 다수의 calculateFee
메시지가 전송된다.