객체는 협력을 위해 존재한다. 협력은 객체가 존재하는 이유를 설명하고 문맥을 제공한다.
유사한 요구사항을 계속 추가하는 상황에서 협력이 서로 다른 패턴을 따른다면 설계의 일관성이 무너지게 된다.
가능하면 유사한 기능을 구현하기 위해 유사한 협력 패턴을 사용하자. 약간의 부조화가 생겨도 시스템 전반적으로 일관성이 지켜진다면 이를 이해하고 확장하기 훨씬 편해진다.
이러한 관점에서 객체지향 패러다임의 장점은 아래와 같다.
객체지향에서 일관성을 유지하는 방법을 예시 코드를 통해 살펴보자.
휴대폰 과금 시스템의 요금 정책을 구현한다. 이는 크게 2가지로 구분된다.
요금 정책은 기본 정책 1개와 부가 정책 여러 개로 이루어진다.
아래 4가지의 기본 정책이 있다.
조합 가능한 모든 경우의 수는 아래와 같다.
e.g. 시간대별 정책
e.g. 여러 날에 걸쳐 통화한 경우
DateTimeInterval
이라는 VO 객체를 추가하자.기존 Call
클래스의 from
, to
필드를 DateTimeInterval
로 대체 가능하다. 요금 계산 로직은 다음 2가지로 나뉘어야 한다.
이렇게 설계를 하면 정보 전문가를 아래와 같이 나타낼 수 있다.
Call
DateTimeInterval
시간대/요일 정책은 날짜 별로 분리된 List<DateTimeInterval>
에 대해 루프를 돌면서 요금을 계산한다.
앞서 시간/요일별 구현은 아래 2가지 문제점이 있다.
이처럼 비일관적인 설계는 다양한 문제를 야기한다. 일단, 새로운 구현 추가를 어렵게 한다. 비슷한 개념의 정책이 추가되는 경우 점점 더 일관성이 깨질 것이며 이는 곧 유연하지 못한 설계가 된다. 일관성이 깨지면 깨질수록 이해하기 어려운 코드가 된다. 결과적으로 유지보수성과 생산성이 떨어질 것이다.
따라서, 유사한 기능을 서로 다른 방식으로 구현해서는 안된다.
협력을 일관성 있게 만들기 위해선 다음 지침을 따라야 한다.
영화마다 여러 개의 할인 정책이 적용될 수 있다. 이를 if
문을 통해 표현할 수 있지만 객체지향에선 아니다.객체지향 코드는 다형성 패턴을 사용하여 이를 구현하여 조건을 판단하지 않는다. 단지 다음 객체로 이동할 뿐이다.
각 조건문을 개별적인 객체로 분리했고 이 객체들과 일관성 있게 협력하기 위해 타입 계층을 구성했다. 변하는 개념을 변하지 않는 개념으로부터 분리했다고 볼 수 있다.
Movie
는 협력하는 DiscountPolicy
의 구체적인 타입을 알지 못한다. 단지, DiscountPolicy
의 퍼블릭 인터페이스를 이해할 수 있을 뿐이다. 이는 변하는 개념을 캡슐화했다고 볼 수 있다.
참고 타입 캡슐화 + 낮은 응집도 유지 기법들
인터페이스 설계 원칙 : 효과적인 캡슐화
의존성 관리 기법 : 타입을 캡슐화하기 위해 낮은 결합도 유지
상속을 재사용 목적으로 사용 X : 캡슐화 저해 + 높은 응집도
LSP : 상속을 타입 계층 구현 목적으로 사용
이처럼 변경에 초점을 두고 캡슐화 관점에서 설계를 바라보면 일관성 있는 협력 패턴을 얻을 수 있다.
변하는 어떤 것이든 감추는 것. 정확히 말하면 소프트웨어 안에서 변할 수 있는 어떤 개념이라도 감추는 것이다. 설계에서 무엇이 변화될 수 있는지 고려하자.
우리는 필드에 private
접근 지정자를 적용하는걸 캡슐화라고 말한다. 이는 데이터 은닉일 뿐 진정한 의미의 캡슐화는 아니다. (데이터 은닉 ⊂ 캡슐화)
설계의 맥락에서 캡슐화를 논의할 때는 그 안에 항상 변경이라는 주제가 녹아있다.
private
DiscountPolicy
의 getDiscountAmount
의 가시성이 protected
코드 수정으로 인한 파급효과를 제어할 수 있는 모든 기법이 캡슐화라고 할 수 있다.
Movie
, DiscountPolicy
)Movie
, XXXDiscountPolicy
)우리는 공통점(변하지 않는 부분)과 차이점(변하는 부분)을 분리해야 한다. 여기선 규칙이 공통점이고 적용 조건이 차이점이라 할 수 있으니 이 둘을 분리하자.
FeeRule
→ FeeCondition
BasicRatePolicy
→ FeeRole
FeeCondition
일관성있는 협력을 이해하면 변하는 부분을 분리해도 전체적인 구조를 쉽게 이해할 수 있다. 이처럼 유사한 기능에 대해 유사한 협력 패턴을 적용하는 것은 객체지향 시스템에서 개념적 무결성을 유지할 수 있는 가장 효과적인 방법이다.
협력을 설계하고 있다면 항상 기존 협력 패턴을 따를 수는 없는지 고민하자. 가급적 기존의 협력 패턴에 맞추는게 가장 좋다. 개념적 무결성을 무너뜨리는 것보다 약간의 부조화를 수용하는게 낫다.
물론, 이러한 일관성은 설계 초기엔 잘 안지켜진다. 요구사항이 바뀔 때 마다 지속적으로 리펙토링을 해야 한다.