: 객체지향적 설계란 응집도, 결합도, 캡슐화의 측면에서 변경이 쉽도록 짜는 것이다.
- 설계는 항상 두가지 사용자를 다 염두해야 한다. 기능을 사용하는 사람, 코드를 건드는 사람
- 설계의 트레이드오프
- (절차지향) 심플하게 만들 것인가?
- (객체지향) 복잡하더라도 변경에 유연하게 만들 것인가?
- 객체지향적, 개발론적인 모든 원칙들은 변경에 관련된 것이다. e.g) SOLID 등
- 다양한 디자인 패턴의 목적은 변경을 감추는 것
- 변경하기 쉬운 설계: 1. 높은 응집도, 2. 낮은 결합도, 3. 캡슐화
팁
- TDD로 개발을 해보면, 메시지를 날리는 것을 중심적으로 설계하게 된다고 한다. TDD는 테스트방법이 아닌 설계방법.
- 프레임워크(바뀌지 않는 것) 코드를 잘 보면 객체지향이 잘 적용되어있다. e.g) 스프링
응집도
: 모듈(클래스) 내부 요소들이 서로 관련 있는 정도.
- 다르게 말하면, 응집도란 모듈 내부 요소들이 함께 변경되는 정도이다.
- 변경 관점에서 보아야 한다.
- 높은 응집도 - 모듈 전체가 동일한 이유로 변경된다.
- 변경될 이유가 하나여야 한다. -> SRP 만족. (SRP는 응집도에 대한 이야기)
- 낮은 응집도 - 모듈이 다양한 이유로 변경된다. -> 단적인 예로 merge 과정에서 conflict가 자주 발생한다.
결합도
: 한 모듈이 다른 모듈에 의존하는 정도. 다른 모듈에 대해 알고 있는 지식의 양
- 결합도가 강하다, 느슨하다로 표현
- 변경 관점에서 보아야한다.
- 느슨한 결합도: 구현(내부)이 변경될 때 함께 변경되지 않는다. 안정적인 추상화(자주 바뀌지 않는 것)에 의존 == 인터페이스에 의존
- 싱글턴: 하나의 오브젝트가 전역적으로 쓰이기 때문에 결합도가 높다.
구현과 추상화
구현과 추상화의 분리
구현 - 자주 변경되는 불안정한 부분. 자주 바뀌면 인터페이스라도 구현이다.
추상화 - 자주 변경되지 않는 안정적인 부분. 인터페이스를 구현했는데 이게 자주바뀌면 아무 의미없다.
e.g) 한 클래스 내에선 다 구현이라고 할 수 있다. getter / setter로 전달해주는 것과 속성을 public으로 노출하는 것은 큰 차이 없다.
캡슐화
: 인터페이스를 외부에 공개하고 자주 변하는 데이터를 인터페이스 뒤로 감춰놓는 것
상태와 행동을 하나로 묶어놓는 것
단순히 getter setter로 묶어놓는 것이 아니라 인터페이스로 노출한다는 관점
타입 캡슐화: 자주 변하는 객체의 타입을 추상화 뒤로 캡슐화
e.g) Movie의 입장에서 DiscountPolicy 타입이 숨겨진다. (AmountDiscount인지 PercentDiscount인지 숨겨진다.)
DIP(의존성 역전 원칙) : 상위 모듈(Movie)과 하위 모듈(AmountDiscount과 PercentDiscount) 모두 추상화(DiscountPolicy)에 의존
컴포지트 디자인 패턴: 갯수가 변하는 것을 감춘다.(캡슐화)
객체지향 설계가 적합할 때?
: 복잡성을 알고리즘에서 분리하고 객체 간의 관계로 만들 수 있다. 유효성 검사, 계산, 파생 등이 포함된 복잡하고 끊임없이 변하는 비즈니스 규칙을 구현해야 한다면 객체 모델을 사용해 비즈니스 규칙을 처리하는 것이 현명하다. - 마틴 파울러
대부분 처음부터 객체지향적으로 잘 설계하는 것은 절대 불가능하다.
- 요구사항이 어떻게 변경될 지 모르기 때문이다.
- 리팩토링이 필수: 변경이 발생할 때마다 변경하기 쉬운 코드로 변경하는 것
- 일단 절차지향적으로 작성하고, 변경이 발생해 응집도가 낮아지고, 결합도가 높아지는 시점마다 객체지향적으로 바꾸기.
Reference)
객체지향의 사실과 오해 조영호님 강의