4. 설계 품질과 트레이드오프
객체지향 설계는 상태(데이터), 책임 으로 분할의 중심축을 삶는 두 가지 방법이 존재
- 훌륭한 설계는 데이터가 아니라 책임에 초점을 맞춰야 한다.
- 상태는 구현에 속한다.
- 구현은 불안정하기 때문에 변하기 쉽다.
- 책임은 인터페이스에 속한다.
- 상태를 캡슐화함으로써, 구현 변경에 대한 파장이 외부로 퍼져나가는 것을 방지한다.
이번 장(ch4) 는 차이를 느끼기 위해 일부러, 데이터의 중심으로 설계해보자
데이터를 준비하자
- 가장 큰 차이는
discountConditions
이 인스턴스 변수로Movie
안에 직접 포함돼 있다는 것
|
|
설계 트레이드오프
캡슐화
상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서이다.
- 구현: 변경될 가능성이 높은 것
- 인터페이스: 상대적으로 안정적인 부분, 변경 가능성이 적은 부분
객체지향 설계의 가장 중요한 원리: 불안정한 구현 세부사항을 안정적인 인터페이스 뒤로 캡슐화 하는 것
복잡성 => 추상화 => 캡슐화
캡슐화란 변경 가능성이 높은 부분을 객체 내부로 숨기는 추상화 기법
- 변경 될 수 있는 어떤 것이라도 캡슐화 해야 한다
유지보수성 이란
- 두려움, 주저함, 저항감 없이 코드를 변경할 수 있는 능력
- 이때, 캡슐화가 가장 중요한 동료
캡슐화를 위반하는 경우 과도한 접근자와 수정자를 가지게 된다.
접근자, 수정자 => 자바의
public get~, public set~
함수사용될 문맥을 추측 할 수밖에 없는 경우 개발자는 어떤 상황에서도 해당 객체가 사용될 수 있게 많은 접근자 메서드를 추가하게 됨
접근자와 수정자에 과도하게 의존하는 설계 방식: 추측에 의한 설계(design-by-guessing strategy)
내부 상태를 드러내는 메서드를 최대한 많이 추가할 수 밖에 없어짐, 내부 구현이 퍼블릭 인터페이스에 그대로 노출됨
응집도와 결합도
높은 응집도와 낮은 결합도를 추구해야 하는 이유는 설계를 변경하기 쉽게 만들기 위함
결합도가 높아도 상관 없는 경우 변경될 확률이 매우 적은 안정적인 모듈에 의존하는 것
- 표준 라이브러리에 포함된 모듈, Ex)
String, List
- 성숙 단계에 포함된 프레임워크
- 표준 라이브러리에 포함된 모듈, Ex)
응집도는 모듈에 포함된 내부 요소들이 연관돼 잇는 정도
- 객체지향 관점에서 응집도는 객체 또는 클래스에 얼마나 관련 높은 책임을 할당했는지를 나타낸다
- 응집도는 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정 가능하다
- 하나의 변경을 수용하기 위해 모듈 전체가 함께 변경된다면 응집도가 높은것
- 하나의 변경에 대해 다수의 모듈이 함께 변경돼야 한다면 응집도가 낮은것
결합도는 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도
- 의존성의 정도를 나타냄
- 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정 가능하다
- 내부 구현을 변경했을 때 이것이 다른 모듈에 영향을 미치는 경우 두 모듈 사이의 결합도가 높다고 한다
데이터 중심 설계에서는 시스템의 어떤 변경도
ReseravtionAgency
의 변경을 유발함, 높은 결합도많은 요구사항 수정사항에 대해
ReseravtionAgency
을 변경해야 함- 할인 정책 추가
- 할인 조건 추가
- 할인 요금 계산 방법 변경
- 예매 요금 계산 방법 변경
로버트 마틴의, 단일 책임 원칙 (SRP)
- 클래스는 단 한가지의 변경 이유만 가져야 한다는 것
- 주의 할점
- 단일 ‘책임’ 원칙 에서의 ‘책임’은 ‘변경의 이유’ 라는 의미로 사용된다
- 역할, ‘책임’, 협력에서 이야기하는 ‘책임’과는 다르며 변경과 관련된 더 큰 개념을 가리킴
자율적인 객체를 향해
캡슐화를 지켜라
Rectangle 클래스는 Int 타입
의top, left, right, bootm
이라는 인스턴스 변수의 존재 사실을 인터페이스를 통해 외부에 노출시킴- 접근자와 수정자는 내부 구현을 인터페이스의 일부로 만들기 때문이다.
- 결과적으로
right, bottom
대신,length, height
를 이용해서 사각형을 표현하도록 수정한다면, 기존의 접근자 메서드(getter, setter) 를 사용하던 모든 코드에 영향을 미침
|
|
스스로 자신의 데이터를 책임지는 객체
- 객체를 설계할 때 데이터에 대해 두 가지 질문이 필요
- 이 객체가 어떤 데이터를 포함해야 하는가?
- 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가?
캡슐화 위반
DiscountCondition
의isDiscountable
함수의 파라미터를 보면, 인스턴스 변수로DayOfWeek
타입의 변수가 있는 것- 시간 정보가 인스턴스 변수로 포함돼 있다느 사실을 외부에 노출함
DiscountCondition
의 속성을 변경하면,isDiscountable
함수와 이를 사용하는 클라이어도 함께 수정되어야 함- 내부 구현의 변경이 외부로 퍼져나가는 파급 효과(ripple effect) 는 캡슐화가 부족하다는 증거
Movie
의 내부 구현을 인터페이스에 노출시키고 있다.calculateAmountDiscountedFee
,calculatePercentDiscountedFee
,calculateNoneDiscountedFee
- 이 함수들은 할인 정책에는
금액, 비율, 미적용
세 가지가 존재한다는 사실을 만천하에 드러낸다.
캡슐화는 단순히 객체 내부의 데이터를 외부로 부터 감추는 것 이상의 의미를 가진다
- 캡슐화는 변경될 수 있는 어떤 것이라도 감추는 것
- 속성의 타입이건, 할인 정책의 종류건 상관 없이 내부 구현의 변경으로 인해 외부의 객체가 영향을 받는 다면 캡슐화를 위반한 것
- 설계에서 변하는 것이 무엇인지 고려하고 변하는 개념을 캡슐화해야 한다.
높은 결합도, 낮은 응집도
DiscountCondition
의 구현을 변경이, 이를 의존하는Movie
변경으로 이어진다.Screening
에서Movie
의isDiscountable
메서드를 호출하는 부분도 함께 변경해야 한다.- 모두 캡슐화를 위반했기 때문이다.
데이터 중심 설계의 문제점
변경에 취약
- 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요
- 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다.
처음부터 데이터에 관해 결정하도록 강요하기에 너무 이른 시기에 내부 구현에 초점을 맞추게 됨
- 데이터는 구현의 일부이다.