좋은 객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 캡슐화를 통해 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 것
-
구현
- 변경될 가능성이 높은 부분
-
인터페이스
- 상대적으로 안정적인 부분
-
변경의 정도에 따라
구현
과인터페이스
를 분리- 외부에서는 인터페이스에만 의존하도록 관계 조절
- 구현 세부사항을 안정적인 인터페이스 뒤로 캡슐화 해야 한다. - OOP의 핵심
-
캡슐화
- 변경될 수 있는 어떤 것이라도 감추는 것
- 내부 구현 변경으로 인해 외부의 객체가 영향을 받으면 캡슐화 위반
- 설계시에 변하는게 무엇인지 고려하고 변하는 개념을 캡슐화하라
-
응집도
- 모듈 내의 요소들이 하나의 목적을 위해 긴밀하게 협력할 수록 높은 응집도
- 응집도가 낮으면 변경시에 여러 모듈을 수정해야함.
-
결합도
- 다른 모듈과의 의존성 정도
- 다른 모듈에 대해 꼭 필요한 지식만을 갖게 해서 결합도를 낮추는게 이상적.
- 결합도가 높으면 변경시에 여러 모듈을 수정해야함.
응집도와 결합도 모두 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정 가능.
getter, setter를 지양하자 (추측에 의한 설계 전략 X)
- getter, setter는 캡슐화를 위반.
- getFee, setFee 등 객체 내부에 fee라는 인스턴스 변수가 존재한다는걸 인터페이스에 나타냄
- 객체가 수행할 책임이 아니라 내부에 저장할 데이터에 초점을 맞췄기 때문에 문제 발생
- 객체 내부 구현이 객체의 인터페이스에 드러난다.
- 객체 내부 구현을 변경하면 해당 인터페이스에 의존하는 모든 클라이언트들도 함께 변경해야 한다. (High coupling)
단일 책임 원칙 : 클래스는 단 한가지의 변경 이유만 가져야한다. - 응집도를 높이는 설계 원칙
캡슐화 -> 높은 응집도, 낮은 결합도
-
객체는 내부 데이터를 감추고 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근.
-
의미있는 객체의 메서드
- = 객체가 책임져야 하는 무언가를 수행하는 메서드
-
객체 스스로 자신의 상태를 처리해야한다.
객체의 인터페이스에 구현이 노출되어 캡슐화가 깨진 경우
isDiscountTable(DayOfWeek dayOfWeek, LocalTime time)
- 해당 메서드는 객체 내부에 DayOfWeek과 LocalTime 타입의 정보가 인스턴스 변수로 포함되어 있다는 사실을 인터페이스를 통해 외부에 노출한다.
- 객체 내부 속성을 변경하면 위 메서드의 파라미터를 수정하고 해당 메서드를 사용하는 모든 클라이언트도 함께 수정되야 한다.
데이터 중심 설계의 문제점
- 너무 이른 시기에 데이터에 관해 결정하도록 강요
- 협력이라는 문맥을 고려하지 않음
- 객체를 고립시킨 채 오퍼레이션 결정
느낀점
많은 프로젝트에서 대부분의 로직은 서비스 레이어에 존재합니다.
로직이 서비스레이어에 존재하게 되면 객체는 단순히 데이터를 제공하는 역할만을 하고 있을 가능성이 높습니다.
가능한 객체 스스로 로직을 처리할 수 있는 구조로 변경해야 하며 서비스 레이어는 레포지토리 레이어와 소통해서 객체에 메시지를 전달하여 도메인이 로직을 처리할 수 있도록 하는 역할에 집중되어야 한다고 생각합니다.
이러한 방식은 객체지향적인 구조를 제공할 뿐만 아니라 테스트를 더 용이하게 해줍니다.
개인적으로 Mock을 사용하면 테스트 코드를 읽기 어려워지고 구현 비용도 크기 때문에 선호하지 않는데, 서비스 레이어의 로직을 객체로 옮기게 되면 Mock을 사용할 필요가 없어지기 때문에 테스트 작성 비용을 줄일 수 있지 않을까 생각해보았습니다.
마지막으로 "좋은 설계란 오늘의 기능을 수행하면서 내일의 변경을 수용할 수 있는 설계다." 라는 말이 인상깊었던 구절이었습니다.
이번장을 읽으면서 떠올렸던 객체지향 생활체조 원칙과 포스트 몇개를 찾아보았습니다.