- 일전의 공부한 내용을 실제로 구현해보면서 공부해보고자 시작한 프로젝트다.
- 이전 글
- 아직 공부 중인 개념입니다. 조금 틀려도 너른 이해 부탁드립니다!
- 이 글은 개인적인 의견을 다룹니다.
다루는 내용
- 디미터 법칙 어떻게 적용할까?
디미터 법칙 어떻게 적용할까?
디미터 법칙이란?
- 디미터 법칙(Law of Demeter)는 객체지향 설계 원칙 중 하나로, 객체 간 결합도를 낮추고 캡슐화를 강화하기 위한 원칙입니다.
- “이웃과만 얘기하라” 라는 원칙입니다.
- 자기 자신
- 자신의 필드/프로퍼티
- 자신이 생성한 객체
- 메소드 매개변수로 받은 객체
- 위 객체들의 메소드 결과 값
- 이를 위반 하는 사례는 아래와 같습니다.
val street = user.getAddress().getStreet().getName()- 이를 지키면 아래와 같습니다.
val street = user.getStreetName()- 왜 하는건가?
효과 설명 캡슐화 내부 객체 구조를 감춥니다. 유지보수성 향상 구조 변경 시 연쇄 변경과 같은 사이드 이펙트 감소(ripple effect) 결합도 감소 호출자가 내부 구조에 덜 의존 테스트 용이성 증가 객체 경로가 단순해짐
과연 “절대적 가치”인가?
- 프로젝트를 진행하면서 마주하는 상황들이 있다.
class Company( val id: String, private var brand: Brand, private val business: Business, private var contact: Contact, private var address: Address, private var representative: Representative,) { val brandName: String get() = brand.name val brandUrl: String get() = brand.url fun changeBrand(brand: Brand) { this.brand = brand }} - 위와 같이 vo로 덮여 있는 객체가 외부에 어떤 구성이 있는지 알리기 싫은 경우
- 알리더라도 vo를 그대로 뱉고 싶지 않은 경우
- vo를 그대로 뱉더라도 외부에서
a.b().c().d()과 같이 지저분해지는 경우 - 이런 경우를 위해서 “사용하는 것” 에는 디미터 법칙을 지키려고 했다.
- 다른 환경에서 Company에 내부에 대해서 알 필요는 없다. 더더욱이 내부 ‘사정’까지 속속들이 알 필요가 없다고 생각했다.
- 그래야 외부 변경과 단절이 되니까.
- 그렇게 해야 Company라는 클래스의 의미를 부여하는 작업이 되니까
그러나 생각보다 복잡해진다.
- 위 구조는 java에서 메소드로 처리하던 것들을 getter를 가진 프로퍼티로 변경한 것들이었다.
- 분명 vo로 의미 단위로 객체를 표현했는데, 어느 날 그 내용물을 다 발골하고 프로퍼티로 작성하는 모습을 볼 수 있었다.
- 물론 필요한 부분만 노출하기 위해서 선택적으로 진행했다.
그러면 어떻게 할 것인가?
- VO자체가 불변인 경우: 불변이면 변경 가능성이 적어져서 바깥으로 내보내고 큰 문제가 없다.
- VO의 필드가 많은 경우: 모든 경우의 수를 하나씩 다 나열해줄 수는 없다. 이런 경우 비용 대비 효율을 생각해서 그냥 내보내자.
- 자주 사용하지 않는 경우: 자주 사용하지 않는 경우까지 생각해서
Company를 어지럽게 하면 우리가 도달하고자 하는 목표에 오히려 멀어질 수도 있다. - 정 그러면 복사하자: Kt에서 copy()는
shallow copy이다. 중첩 VO를 내보내야 하고 이 과정이 우려가 된다면 copy를 통해서deep copy를 해서 내보내는 방법이 있을 것으로 본다. - KotlinDSL로 작성하자: Lambda와 같은 형식으로 외부에서 받아서 Apply를 한다거나 KotlinDSL로 구성해서 복잡한 작업을 외부로 돌릴 수 있다.