04. 설계 품질과 트레이드 오프
태그
비어 있음
날짜
2024년 4월 4일
책임이 객체지향 애플리케이션 전체의 품질을 결정한다. 책임을 할당하는 작업이 응집도와 결합도 같은 설계 품질과 깊이 연관되어 있다. 훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다. 객체를 단순한 데이터의 집합으로 바라보는 시각은 설계가 변경에 취약해진다. 이번 장에서는 데이터 중심 설계와 객체지향적으로 설계한 구조의 차이를 통해, 훌륭한 객체 지향 설계의 다양한 특징과 책임 할당 원칙을 알아보자.
데이터 중심의 영화 예매 시스템
데이터 중심의 설계란 객체 내부에 저장되는 데이터를 기반으로 시스템을 분할하는 방법이다.
‘데이터가 무엇인가’를 묻는 것으로 시작한다.
Movie, DiscountCondition, Screening, Reservation, Customer, ReservationAgency 클래스를 데이터 중심의 클래스를 구현했다.
설계 트레이드오프
데이터 중심 설계와 책임 중심 설계 방법을 비교해보자
캡슐화, 응집도, 결합도를 품질 척도로 비교한다.
캡슐화
변경될 가능성이 높은 부분을 구현, 상대적으로 안정적인 부분을 인터페이스라고 부른다.
구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하도록 관계를 조절하자.
객체지향 프로그래밍을 통해 전반적으로 얻을 수 있는 장점은 오직 설계 과정 동안 캡슐화를 목표로 인식할 때만 달성 될 수 있다.
캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법이다.
캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제할 수 있기 때문이다.
응집도와 결합도
응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다.
객체지향 관점에서 응집도는 객체 또는 클래스에 얼마나 관련 높은 책임들을 할당했는지를 나타낸다.
결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도다.
객체지향의 관점에서 결합도는 객체 또는 클래스가 협력에 필요한 적절한 수준의 관계만을 유지하고 있는 지를 나타낸다.
⇒ 응집도와 결합도는 변경과 관련된 것이다.
높은 응집도와 낮은 결합도를 가진 설계를 추구해야 하는 이유는 단 한가지, 그것이 설계를 변경하기 쉽게 만들기 때문이다.
변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다.
결합도는 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다.
클래스의 구현이 아닌 인터페이스에 의존하도록 코드를 작성해야 낮은 결합도를 얻을 수 있다.
캡슐화의 정도가 응집도와 결합도에 영향을 미친다.
응집도와 결합도를 고려하기 전에 먼저 캡슐화를 향상시키기 위해 노력하라.
데이터 중심의 영화 예매 시스템의 문제점
데이터 중심의 설계는 캡슐화를 위반하기 쉽기 때문에 책임 중심의 설계보다 응집도가 낮고 결합도가 높은 객체들을 양상하게 될 가능성이 높다.
문제점
캡슐화 위반
movie에 fee라는 이름의 인스턴스 변수가 존재한다는 사실을 퍼블릭 인터페이스에 노골적으로 드러낸다.
높은 결합도
여러 데이터 객체들을 사용하는 제어 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다는 것이다.
낮은 응집도
ReservationAgency는 다음과 같은 수정 사항이 발생하는 경우 코드를 수정해야 한다.
할인 정책 추가, 할인 정책별로 할인요금 계산하는 방법 변경, 할인 조건 추가 등
낮은 응집도는 변경과 상관 없는 코드들이 영향을 받게 되고 하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다.
자율적인 객체를 향해
캡슐화를 지켜라
객체에게 의미 있는 메서드는 객체가 책임져야 하는 무언가를 수행하는 메서드다. (단순한 get,set아님)
Rectangle 클래스의 캡슐화 강화 - 자신의 크기를 스스로 증가시키도록 ‘책임을 이동’
Java
복사
//외부 클래스
rectangle.setRight(rectangle.getRight()*multiple)
rectangle.setBottom(rectangle.getBottom()*multiple)
Java
복사
public void enlarge(int multiple){
right *= multiple;
bottom *= multiple;
}
스스로 자신의 데이터를 책임지는 객체
객체 내부의 데이터보다 객체가 협력하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다.
데이터에 대한 고민 + “이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가?”
결합도 측면에서 개선된 설계
하지만 여전히 부족하다
두 번째 설계가 캡슐화 관점에서 향상되었지만 만족스러울 정도는 아니다.
여전히 문제가 발생한다.
캡슐화 위반
객체 내부에 DayOfWeek 타입의 요일과 LocalTime 타입의 시간 정보가 인스턴스 변수로 포함돼 있다는 사실을 인터페이스를 통해 외부에 노출하고 있다.
DiscountCondition 의 속성을 변경하면 isDiscountable과 이 메서드를 사용하는 모든 클라이언트도 함께 수정해야 한다.
⇒ 내부 구현의 변경이 외부로 퍼져나가는 파급효과는 캡슐화가 부족하다는 명백한 증거다.
캡슐화의 진정한 의미
단순히 객체 내부의 데이터를 외부로부터 감추는 것 이상의 의미를 가진다.
캡슐화는 변경될 수 있는 어떤 것이라도 감추는 것을 의미한다.
내부의 구현의 변경으로 인해 외부의 객체가 영향을 받는다면 캡슐화를 위반한 것이다.
높은 결합도
DiscountCondition의 기간 할인 조건의 명칭이 PERIOD에서 다른 값으로 변경된다면 Movie를 수정해야 한다. DiscountCondition의 종류가 추가되거나 삭제된다면 Movie 안의 if ~ else 구문을 수정해야 한다.
⇒ DiscountCondition의 구현을 변경할 경우 Movie를 변경해야 한다는 것은 두 객체 사이의 결합도가 높다는 것을 의미한다.
낮은 응집도
DiscountCondition이 할인 여부를 판단하는 데 필요한 정보가 변경된다면 결국 Screening에서 Movie의 메서드를 호출하는 부분도 변경해야 한다.
⇒ 하나의 변경을 수용하기 위해 코드의 여러 곳을 동시에 변경해야 한다는 것은 설계의 응집도가 낮다는 증거다.
데이터 중심 설계의 문제
두 번째 설계가 변경에 유연하지 못한 이유는 캡슐화를 위반했기 때문이다.
데이터 중심의 설계가 변경에 취약한 이유는 두 가지다.
데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
데이터를 먼저 결정하고 필요한 오퍼레이션을 나중에 결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 된다.
결과적으로 캡슐화에 실패하고 변경에 취약해진다.
올바른 객체지향 설계의 무게 중심은 객체의 내부가 아니라 외부에 맞춰져 있어야 한다. 객체가 내부에 어떤 상태를 가지고 어떻게 관리하는지는 부가적인 것이고 중요한 것은 객체가 다른 객체와 협력하는 방법이다.
데이터 중심 설계에서는 객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문이 이미 구현된 객체의 인터페이스를 억지로 끼워 맞출 수밖에 없다.
느낀점
3장에서 설명했던 협력이 이해가 됐다.
왜 데이터 중심의 설계가 안 좋은지, 어떻게 안좋은지, 캡슐화를 위반하는 이유가 뭔지 예제로 보니까 이해가 잘됐다. 그리고 지금까지 데이터 중심의 설계를 했다는 것을 깨달았다.
또한 캡슐화를 하기 위해서 여러번 코드를 리팩토링 하는 과정이 필요하겠다는 생각을 했다.
변경에 관한 어떤 것을 캡슐화하라는 말이 인상깊었다.
설계를 할 때 객체의 책임과 역할을 먼저 설계하고 오퍼레이션을 먼저 결정하는 방식으로 해봐야겠다.
표
'책 > 오브젝트' 카테고리의 다른 글
06. 메시지와 인터페이스 (0) | 2025.03.17 |
---|---|
05. 책임 할당하기 (0) | 2025.03.17 |
03. 역할, 책임, 협력 (0) | 2025.03.17 |
02. 객체지향 프로그래밍 (0) | 2025.03.17 |
01. 객체, 설계 (0) | 2025.03.17 |