오브젝트: 일관성 있는 협력 패턴으로 유지보수성 높이기
- 재사용을 위해서는 객체들의 협력 방식을 일관성있게 만들어야 한다.
- 일관성 있게 만들게 되면 설계에 드는 비용을 감소 시킬 수 있고 코드를 이해하는 것이 더 쉬워진다.
1. 핸드폰 과금 시스템 변경하기
기본 정책 확장
예제 - 핸드폰 요금 기본 정책을 구성하는 방식을 네 가지로 확장한다
- 고정 요금방식
- 시간대별 방식
- 요일별 방식
- 구간별 방식
각 방식을 구현 할 때 일관성이 없으면 두 가지 상황에서 문제가 발생한다.
- 새로운 구현을 추가하는 상황
- 새로운 구현을 추가할 때 어떤 방식으로 하던 구현은 가능하다. 그러나 새로운 정책을 추가하면 추가할 수록 일관성이 더 어긋나게 된다
- 기존의 구현을 이해하는 상황
- 일관성이 없으면 어느 한 구현을 이해한다고 해서 다른 방식의 구현을 쉽게 이해할 수 없다. 오히려 코드를 이해 하는데 오히려 더 방해가 된다.
즉, 유사한 기능은 유사한 방식으로 구현해야한다.
2. 설계에 일관성 부여하기
일관성 있는 설계를 만들기 위한 조언
- 다양한 설계 경험을 익힌다.
- 시간이 걸리는 방법이다.
- 널리 알려진 디자인 패턴을 학습하고 변경이라는 문맥 안에서 디자인 패턴을 적용 해 본다.
- 모든 경우에 적합한 패턴을 찾을 수 있는 것은 아니다. 따라서 협력을 일관성 있게 만들기 위해 아래와 같은 기본 지침을 따르는 것이 좋다.
- 1. 변하는 개념을 변하지 않는 개념으로부터 분리하라
- 2. 변하는 개념을 캡슐화하라
- 추상화, 구현체를 사용한다
- 모든 경우에 적합한 패턴을 찾을 수 있는 것은 아니다. 따라서 협력을 일관성 있게 만들기 위해 아래와 같은 기본 지침을 따르는 것이 좋다.
하나의 클래스에 있는 조건 로직을 객체 사이의 이동으로 바꿀려면 클래스를 더 작은 클래스로 분리해야한다.
중요) 분리할 때 중요 기준은 코드 변경의 이유와 주기다. (결국 단일 책임을 지키도록 분리하는 것이 목적이다)
- 단 하나의 이유에서만 변경돼야 한다
- 클래스 안에 모든 코드는 함께 변경돼야 한다
작은 클래스로 분리하면 아래와 같은 장점이 생긴다.
- 인스턴스들 사이의 협력 패턴에 일관성을 부여하기가 쉬워진다.
- 유사한 행동을 하는 작은 클래스들은 자연스럽게 역할이라는 추상화로 묶이고, 역할 사이에서 이뤄지는 협력 방식이 전체 설계의 일관성을 유지할 수 있게 도와준다.
캡슐화 다시 살펴보기
캡슐화는 단순히 데이터를 감추는 것이 아니고, 소프트웨어 안에서 변할 수 있는 어떤 개념이라도 감추는 것이다.
데이터 캡슐화 외에도 여러 종류의 캡슐화가 있다.
- 데이터 캡슐화
- 메서드 캡슐화
- 객체 캡슐화
- 서브 타입 캡슐화
- 다형성의 기반
협력을 일관성 있게 만들기 위한 가장 일반적인 방법은 서브 타입 캡슐화와 객체 캡슐화를 조합하는 것이다.
- 서브타입 캡슐화는 인터페이스를 상속한다.
- 변하지 않는 부분으로부터 변하는 부분을 분리한다.
- 변하는 부분들의 공통적인 행동을 추상 클래스나 인터페이스로 추상하고, 변하는 부분들이 이 추상 클래스나 인터페이스를 상속받게 만든다.
- 변하는 부분은 변하지 않는 부분의 서브타입이 된다.
- 객체 캡슐화는 합성을 사용한다.
- 위에서 구현한 타입 계층을 변하지 않는 부분에서 합성한다.
- 변하지 않는 부분에서는 변경되는 구체적인 코드에 결합 되서는 안되기 때문에 상속을 사용하지 않는다.
3. 일관성 있는 정책 구현하기
변경 분리하기
일관성있는 협력을 만들기 위해서 우선 변하는 개념과 변하지 않는 개념을 분리한다.
변하지 않는 개념을 찾기 위해 각 정책 사이에 공통점을 찾아본다.
공통점 - 각 기본정책을 구성하는 방식이 유사
- 기본정책은 한 개 이상의 규칙으로 구성
- 하나의 규칙은 적용조건과 단위 요금의 조합
변하지 않는 “규칙”으로부터 변하는 “적용 조건”을 분리한다.
변경 캡슐화하기
협력을 일관성 있게 만들기 위한 다음스텝으로 변경을 캡슐화해서 변경에 대한 파급효과를 줄인다.
변경을 캡슐화하는 가장 좋은 방법은 아래 세 단계이다.
- 변하지 않는 부분으로부터 변하는 부분을 분리한다.
- 변하는 부분의 공통점을 추상화한다.
- 변하지 않는 부분은 2번의 추상화만 의존한다.
예제에 적용해보면
- 규칙으로부터 적용조건을 분리해서 추상화한다.
- 시간대별, 요일별, 구간별 방식을 추상화의 서브타입으로 만든다. → 서브타입의 캡슐화
- 규칙이 적용조건을 표현하는 추상화를 합성 관계로 연결한다 → 객체의 캡슐화
변하지 않는 부분은 “기본 정책이 여러 규칙들의 집합이고, 하나의 규칙은 적용 조건과 단위 요금으로 구성된다는 것”이다.
변하는 부분은 적용 조건의 세부적인 내용이다.
협력 패턴 설계하기
- 객체들의 협력방식을 고민한다.
- 변하지 않는 부분만을 이용해 객체 사이의 협력을 이야기할 수 있다.
- 올바른 방향으로 나아가고 있는지 확인할 수 있는 유일한 방법은 협력을 직접 구현하는 것이다.
추상화 수준에서 협력 패턴 구현하기
- 변하지 않는 요소와 추상적인 요소만으로도 요금 계산에 필요한 전체적인 협력 구조를 설명할 수 있다.
구체적인 협력 구현하기
- 변하는 것을 서브타입으로 구현한다. 즉, 인터페이스를 실체화하는 클래스를 구현한다
- 변경을 캡슐화해서 협력을 일관성 있게 만들면 어떤 장점이 있는지 알 수 있게 된다.
- 변하지 않는 부분을 재사용할 수 있다.
- 새로운 기능을 추가하기 위해서는 변하는 부분만 구현하게 된다. 즉 변경이 쉬워진다.
- 코드의 재사용성과 테스트해야하는 코드의 양이 감소한다.
- 구조를 강제하기 때문에 추가 및 변경 시 일관성을 유지 할 수 있다.
객체지향에서 개념적 무결성을 유지할 수 있는 가장 효과적인 방법은 유사한 기능에 유사한 협력패턴을 사용하는 것이다.
협력 패턴에 맞추기
협력 패턴의 일관성을 유지하기 위해 개념적으로 불필요한 클래스와 메소드를 만들어야 할 수 도 있다.
그러나 맹목적으로 일관성을 맞추라는 것은 아니다. 달라지는 변경의 방향에 맞춰 지속적으로 코드를 개선해야한다.
협력 패턴이 요구사항이 계속해서 추가되는 과정에서 일관성이 깨지는 경우 발생 할 수 있다. 이는 자연스러운 현상이며 협력 패턴이 변경의 무게를 지탱하기 어렵다면 변경을 수용할 수 있도록 협력 패턴을 과감하게 리팩터링한다.
패턴을 찾아라
애플리케이션에서 유사한 기능에 대한 변경이 지속적으로 발생하고 있다면?
- 변경을 캡슐화할 수 있는 적절한 추상화를 찾은 후, 이 추상화에 변하지 않는 공통적인 책임을 할당해라.
- 현재 구조가 변경을 캡슐화하기에 적합하지 않다면 코드를 수정하지 않고도 원하는 변경을 수용할 수 있도록 협력과 코드를 리팩토링 해라