Demeter's Law는 “객체는 자신과 밀접하게 관련된 객체들(친구) 외의 다른 객체들과 상호작용하면 안 된다”는 원칙이다.
다시 말해, 한 객체가 다른 객체의 내부 구조나 하위 구성요소에 지나치게 의존하지 않아야 한다는 뜻이다.
이 원칙은 1987년 Northeastern University의 연구 프로젝트에서 처음 제안되었으며, 그 이름은 그리스 신화의 여신 데메테르(Demeter)에서 유래했다.
디미터 법칙의 핵심 원칙은 다음과 같다.
Don't talk to strangers
핵심 원칙을 읽어보면 결합도를 느슨하게 하는 것이 목적임을 쉽게 알 수 있다.
디미터 법칙을 위반하는 예시
여러 객체에 대해 체인 호출(Chained Calls) 을 통해 내부 객체에 직접 접근하고 있다.
String zipCode = person.getAddress().getStreet().getZipCode();
문제점
person
의 zipCode
를 얻고 있다.Person
내부의 구조(Address
, Street
)에 대해 알아야 한다.→ 객체 간의 결합도가 높아져 유지보수가 어려워진다.
디미터 법칙을 준수하도록 개선한 예시
객체 간의 세부 구조를 감추고, 클래스에 필요한 기능을 위임해 클라이언트가 단순히 person 객체와만 상호작용하도록 개선해보자.
String zipCode = person.getZipCode();
단순히 메서드를 추가하는 것만으로 쉽게 해결할 수 있다.
개선점
getCustomerCity()
메서드를 추가하여 내부의 Customer와 Address 객체에 대한 접근을 숨겼다.Person
객체의 메서드만 호출하면 되므로, 객체의 내부 구조 변경 시 클라이언트 코드가 영향을 받지 않는다.→ 객체 간 결합도를 낮춰 유지보수성과 확장성을 높인다.
디미터 법칙을 준수하면 각 객체의 내부 구현을 감추고, 인터페이스를 통해서만 상호작용하게 되어, 코드의 결합도가 낮아지고 유지보수가 용이해진다.
혹시 내 코드는 무분별하게 체인 호출을 하고 있진 않은가 점검해보자.
디미터의 법칙 위반 여부를 판단하는 기준
일반적으로 “도트(.)을 여러 번 찍지 마라”라고 직관적으로 설명되어 디미터의 법칙을 접하면 단순히 도트(.)를 여러 번 사용하지 말라는 법칙이라고 느껴질 수 있다. 대부분 상황에선 맞는 말이긴 해서 오해의 소지가 생길 수 있다.
디미터의 법칙은 객체가 다른 객체의 내부 구조에 의존하지 않도록 설계하는 원칙이라는 본질을 생각해보면 도트를 여러번 쓰더라도 디미터의 법칙과는 관계가 없는 경우도 있다.
(객체는 자신이 직접 가진 속성이나 메서드, 또는 자신이 생성한 객체의 메서드만 호출해야 한다)
Stream API 자체는 객체의 내부 구조를 노출하는 것이 아니라 컬렉션을 다루는 도구이므로, 디미터의 법칙과 직접적인 관련이 없다.
Stream의 메서드 체이닝에선 같은 Stream이 반환되기 때문에, 도트(.)를 여러 번 사용하는 건 문제가 되지 않는다.
물론 아래의 코드처럼 객체를 사용하면 Stream과 무관하긴 하지만 위반할 수도 있다.
List<String> usernames = getMembers().stream()
.map(member -> member.getProfile().getUsername()) // getProfile()을 통해 내부 구조에 직접 접근
.collect(Collectors.toList());
DTO는 본질적으로 데이터 전달 용도라 내부 구조를 직접 탐색해도 문제가 되지 않는다.
DTO는 도메인 객체가 아니며, 비즈니스 로직도 갖지 않는다.
디미터의 법칙은 객체 간의 결합도를 낮추기 위한 설계 원칙이라는 본질을 생각해보면, 자료구조에 디미터의 법칙을 엄격하게 적용하는 의미는 없다.