오트젝트 05 책임 할당하기
[오브젝트: 코드로 이해하는 객체지향 설계] 를 읽고 정리한 내용입니다.
책임에 초점을 맞춰서 설계할 때 직면하는 가장 큰 어려움은 어떤 객체에게 어떤 책임을 할당할지를 결정하기가 쉽지 않다는 것이다. 책임 할당 과정은 일종의 트레이드오프 활동이다. 동일한 문제를 해결할 수 있는 다양한 책임 할당 방법이 존재하며, 어떤 방법이 최선인지는 상황과 문맥에 따라 달라진다. 따라서 올바른 책임을 할당하기 위해서는 다양한 관점에서 설계를 평가할 수 있어야 한다.
책임 주도 설계를 향해
데이터는 객체가 책임을 수행하는 데 필요한 재료를 제공할 뿐이다. 객체지향에 갓 입문한 사람들이 가장 많이 저지르는 실수가 바로 객체의 행동이 아니라 데이터에 초점을 맞추는 것이다. 너무 이른 시기에 데이터에 초점을 맞추면 객체의 캡슐화가 약화되기 때문에 낮은 응집도와 높은 결합도를 가진 객체들로 넘쳐나게 된다. 그 결과로 얻게 되는 것은 변경에 취약한 설계다.
- 섣불리 예측해서 코드를 작성하지 말자.
데이터보다 행동을 먼저 결정하라
상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서다. 여기서 구현이란 나중에 변경될 가능성이 높은 어떤 것을 가리킨다. 객체지향이 강력한 이유는 한 곳에서 일어난 변경이 전체 시스템에 영향을 끼치지 않도록 파급효과를 적절하게 조절할 수 있는 장치를 제공하기 때문이다.
- 인터페이스는 구현과 반대로 변경이 적어야하기 때문에 안정적이다.
협력이라는 문맥안에서 책임을 찾아라
객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 해야 한다
책임 할당을 위한 GRASP 패턴
많은 사람들이 도메인 모델은 구현과는 무관하다고 생각하지만 이것은 도메인 모델의 개념을 오해한 것에 불과하다. 도메인 모델은 도메인을 개념적으로 표현한 것이지만 그 안에 포함된 개념과 관계는 구현의 기반이 돼야 한다. 이것은 도메인 모델이 구현을 염두에 두고 구조화되는 것이 바람직하다는 것을 의미한다. 반대로 코드의 구조가 도메인을 바라보는 관점을 바꾸기도 한다
- 도메인과 코드의 구조는 서로 상호작용하며 서로에게 영향을 끼친다.
높은 응집도와 낮은 결합도
다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다.
- 가장 좋은 케이스, 하지만 현실에서 이 둘은 반비례하는 경우가 많다.
구현을 통한 검증
응집도가 낮다는 것은 서로 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐져 있다는 것을 의미한다. 따라서 낮은 응집도가 초래하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다.
코드를 통해 변경의 이유를 파악할 수 있는 첫 번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다. 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨진다.
따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다.
코드를 통해 변경의 이유를 파악할 수 있는 두 번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. 모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼 수 있다. 반면 메서드들이 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다.
- 응집도와 결합도는 다양한 곳에서 영향을 받는다. 여기서는 코드가 변경이 필요할 때 몇가지의 변경 이유가 필요한지 체크하는 것, 변수가 초기화되는 시점 등을 이야기한다.
다형성을 통해 분리하기
구현을 공유할 필요 없이 역할을 대체하는 객체들의 책임만 정의하고 싶다면 인터페이스를 사용하면 된다.
다시 말해 객체의 타입에 따라 변하는 행동이 있다면 타입을 분리하고 변화하는 행동을 각 타입의 책임으로 할당하라는 것이다. GRASP에서는 이를 POLYMORPHISM(다형성) 패턴이라고 부른다.
Movie의 관점에서 DiscountCondition의 타입이 캡슐화된다는 것은 새로운 DiscountCondition 타입을 추가하더라도 Movie가 영향을 받지 않는다는 것을 의미한다. Movie에 대한 어떤 수정도 필요 없다. 오직 DiscountCondition 인터페이스를 실체화하는 클래스를 추가하는 것으로 할인 조건의 종류를 확장할 수 있다.이처럼 변경을 캡슐화하도록 책임을 할당하는 것을 GRASP에서는 PROTECTED VARIATIONS(변경 보호) 패턴이라고 부른다.
설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화하라
여기서 하고 싶은 말은 책임 주도 설계 방법에 익숙하지 않다면 일단 데이터 중심으로 구현한 후 이를 리팩터링하더라도 유사한 결과를 얻을 수 있다는 것이다. 처음부터 책임 주도 설계 방법을 따르는 것보다 동작하는 코드를 작성한 후에 리팩터링하는 것이 더 훌륭한 결과물을 낳을 수도 있다. 캡슐화, 결합도, 응집도를 이해하고 훌륭한 객체지향 원칙을 적용하기 위해 노력한다면 책임 주도 설계 방법을 단계적으로 따르지 않더라도 유연하고 깔끔한 코드를 얻을 수 있을 것이다.