[Object] 4장
4. 설계 품질과 트레이드 오프
객체지향 설계의 핵심은 역할, 책임, 협력이다. 그 중 객체지향 애플리케이션 전체의 품질을 결정하는 것은 책임이다.
객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다. 이 정의에는 객체지향 설계에 관한 두 가지 관점이 섞여 있다. 첫 번째는 객체지향 설계의 핵심이 책임이라는 것이다. 두 번째는 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 깊이 연관돼 있다는 것이다.
훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다. 적절한 비용안에서 쉽게 변경할 수 있는 설계는 응집도가 높고 서로 느슨하게 결합돼 있는 요소로 구성된다. 이러한 설계를 위해서는 객체의 상태가 아니라 객체의 행동에 초점을 맞춰야한다. 객체를 단순한 데이터의 집합으로 바라보는 시각은 객체 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기 때문에 결과적으로 설계가 변경에 취약해진다.
데이터 중심의 영화 예매 시스템
객체지향 설계에서는 두 가지 방법을 이용해 시스템을 객체로 분할할 수 있다.
- 상태를 분할의 중심축으로 삼는 방법
- 책임을 분할의 중심축으로 삼는 방법
데이터 중심의 관점에서 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의한다. 책임 중심의 관점에서 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관한다.
객체의 상태는 구현에 속한다. 구현은 불안정하기 때문에 변하기 쉽다. 데이터에 초점을 맞추는 설계는 변경에 취약할 수 밖에 없다.
데이터를 준비하자
책임 중심의 설계가 책임이 무엇인가를 묻는 것으로 시작한다면 데이터 중심의 설계는 객체가 내부에 저장해야 하는 데이터가 무엇인가를 묻는 것으로 시작한다.
그림 4.1 영화 예매 시스템 구현을 위한 데이터 클래스
설계 트레이드오프
캡슐화
상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서다. 변경될 가능성이 높은 부분을 구현이라고 부르고 상대적으로 안정적인 부분을 인터페이스라고 부른다. 객체를 설계하기 위한 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하도록 관계를 조절하는 것이다.
객체지향에서 가장 중요한 원리는 캡슐화다. 캡슐화는 외부에서 알 필요가 없는 부분을 감춤으로써 대상을 단순화하는 추상화의 한 종류다.
설계가 필요한 이유는 요구사항이 변경되기 때문이고, 캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제할 수 있기 때문이다. 정리하면 캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법이다. 변경될 수 있는 어떤 것이라도 캡슐화해야한다.
응집도와 결합도
응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다. 모듈 내의 요소들이 하나의 목적을 위해 긴말하게 협력한다면 그 모듈은 높은 응집도를 가진다. 결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도다. 어떤 모듈이 다른 모듈에 대해 너무 자세한 부분까지 알고 있다면 두 모듈은 높은 결합도를 가진다.
좋은 설계란 높은 응집도와 낮은 결합도를 가진 모듈로 구성된 설계를 의미한다. 또한, 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계다.
변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다. 응집도가 높은 설계에서는 하나의 요구사항을 변경하기 위해 오직 하나의 모듈만 수정하면 된다. 반면 응집도가 낮은 설계에서는 하나의 원인에 의해 변경해야 하는 부분이 다수의 모듈에 분산돼 있기 때문에 여러 모듈을 동시에 수정해야 한다.
결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다. 내부 구현을 변경했을 때 이것이 다른 모듈에 영향을 미치는 경우에는 두 모듈 사이의 결합도가 높다고 표현한다.
데이터 중심의 영화 예매 시스템의 문제점
데이터 중심의 설계가 가진 대표적인 문제점을 다음과 같이 요약할 수 있다.
- 캡슐화 위반
- 높은 결합도
- 낮은 응집도
캡슐화 위반
public class Movie {
private Money fee;
public Money getFee() {
return fee;
}
public void setFee(Money fee) {
this.fee = fee;
}
}
getFee 메서드와 setFee 메서드는 Movie 내부에 Money 타입의 fee라는 이름의 인스턴스 변수가 존재한다는 사실을 퍼블릭 인터페이스에 노골적으로 드러낸다.
앨런 홀럽(Allen Holub)은 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한 설계(design-by-guessing strategy) 이라고 부른다.
높은 결합도
데이터 중심 설계는 객체의 캡슐화를 약화시키기 때문에 클라이언트가 객체의 구현에 강하게 결합된다. 결합도 측면에서 데이터 중심 설계가 가지는 또 다른 단점은 여러 데이터 객체들을 사용하는 제어 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다는 것이다.

그림 4.2 너무 많은 대상에 의존하기 때문에 변경에 취약한 ReservationAgency
낮은 응집도
서로 다른 이유로 변경되는 코드가 하나의 모듈안에 공존할 때 모듈의 응집도가 낮다고 말한다. ReservationAgency를 예로 들어 변경과 응집도 사이의 관계를 살펴보자. 아마 다음과 같은 수정사항이 발생하는 경우에 ReservationAgency의 코드를 수정해야 할 것이다.
- 할인 정책이 추가될 경우
- 할인 정책별로 할인 요금을 계산하는 방법이 변경될 경우
- 할인 조건이 추가되는 경우
- 할인 조건별로 할인 여부를 판단하는 방법이 변경될 경우
- 예매 요금을 계산하는 방법이 변경될 경우
낮은 응집도는 두 가지 측면에서 설계에 문제를 일으킨다.
- 변경의 이유가 서로 다른 코드들을 하나의 모듈 안에 뭉쳐놓았기 때문에 변경과 아무 상관이 없는 코드들이 영향을 받게 된다.
- 하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다.
자율적인 객체를 향해
캡슐화를 지켜라
객체는 자신이 어떤 데이터를 가지고 있는지를 내부에 캡슐화하고 외부에 공개해서는 안된다. 객체는 스스로의 상태를 책임져야 하며 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.
스스로 자신의 데이터를 책임지는 객체
우리가 상태와 행동을 객체라는 하나의 단위로 묶은 이유는 객체 스스로 자신의 상태를 처리할 수 있게 하기 위해서다. 객체 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다.
그림 4.3 결합도 측면에서 그림 4.2보다 개선된 설계
데이터 중심 설계의 문제점
데이터 중심의 설계가 변경에 취약한 이유는 두 가지다.
- 데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
- 데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다
데이터를 먼저 결정하고 데이터를 처리하는데 필요한 오퍼레이션을 나중에 결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 된다. 결과적으로 객체의 인터페이스는 구현을 캡슐화하는 데 실패하고 코드는 변경에 취약해진다.
데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다.
협력이라는 문맥 안에서 필요한 책임을 결정하고 이를 수행할 적절한 객체를 결정하는 것이 가장 중요하다. 올바른 객체지향 설계의 무게 중심은 항상 객체의 내부가 아니라 외부에 맞춰져 있어야 한다. 안타깝게도 데이터 중심 설계에서 초점은 객체의 외부가 아니라 내부로 향한다.
Leave a comment