데메테르 법칙으로 객체 협력 경로를 줄이는 설계
데메테르 법칙의 최소 지식 원칙과 메서드 체인 개선 방식, 객체 간 결합도를 낮추는 설계 전략을 정리한다.
2026-08-14 · 최초 발행 2025-05-23
객체가 알아야 할 범위를 제한한다
데메테르 법칙(Law of Demeter)은 객체 지향 설계에서 객체 간 결합도를 줄이기 위한 원칙이다. 최소 지식의 원칙(Principle of Least Knowledge)으로도 불리며, 객체가 협력할 때 필요한 범위 이상의 내부 정보를 알지 않도록 제한한다.
한 객체는 보통 자신, 메서드 매개변수로 전달된 객체, 메서드 안에서 생성한 객체, 직접 관리하는 컴포넌트 객체, 메서드에서 접근 가능한 전역 변수에 메시지를 보낼 수 있다. 핵심은 객체가 간접적으로 연결된 대상의 내부 구조까지 따라 들어가지 않는 데 있다.
이 원칙은 흔히 “낯선 사람에게 말하지 말라(Don't talk to strangers)”로 설명된다. 다른 객체의 내부 구조를 많이 알수록 그 구조가 바뀔 때 영향을 받는 코드도 늘어난다.
메서드 체인이 드러내는 내부 의존성
다음 코드는 Customer 내부의 Wallet, 다시 그 안의 Money까지 따라가 금액을 확인한다.
public class Customer {
private Wallet wallet;
public Wallet getWallet() {
return wallet;
}
}
public class Wallet {
private Money money;
public Money getMoney() {
return money;
}
}
public class Money {
private int amount;
public int getAmount() {
return amount;
}
}
// 데메테르 법칙 위반 코드
public class PaymentProcessor {
public void processPayment(Customer customer, int price) {
if (customer.getWallet().getMoney().getAmount() >= price) {
// 결제 처리
}
}
}
PaymentProcessor는 결제 가능 여부만 판단하면 되지만, Customer의 내부 구성과 Wallet, Money의 접근 경로까지 알고 있다. customer.getWallet().getMoney().getAmount() 같은 체인은 여러 객체의 내부 구조에 의존하게 만든다.
책임을 가진 객체에 판단을 맡긴다
금액 확인 책임을 Customer와 Wallet 쪽으로 옮기면 호출자는 내부 구조를 알 필요가 없다.
public class Customer {
private Wallet wallet;
public boolean hasEnoughMoney(int amount) {
return wallet.hasEnoughMoney(amount);
}
}
public class Wallet {
private Money money;
public boolean hasEnoughMoney(int amount) {
return money.getAmount() >= amount;
}
}
// 데메테르 법칙을 준수한 코드
public class PaymentProcessor {
public void processPayment(Customer customer, int price) {
if (customer.hasEnoughMoney(price)) {
// 결제 처리
}
}
}
이제 PaymentProcessor는 Customer에게 충분한 금액이 있는지만 요청한다. Wallet과 Money의 존재나 연결 방식은 Customer 내부에 남으며, 객체 사이의 결합도도 낮아진다.
이런 구조는 내부 구현 보호를 강화하고, 한 객체의 변경이 다른 객체에 미치는 영향을 줄인다. 객체를 독립적으로 테스트하기 쉬워지며, 느슨하게 결합된 객체는 다른 컨텍스트에서도 재사용하기 수월하다.
서비스 경계에도 같은 원칙을 적용할 수 있다
Spring, Django와 같은 프레임워크는 서비스 계층이 데이터 접근 계층의 세부 구현을 직접 다루기보다 인터페이스를 통해 협력하는 구조를 권장한다.
마이크로서비스 아키텍처에서는 이 관점이 서비스 경계로 확장된다. 각 서비스는 다른 서비스의 내부 구현을 알지 못하고, 정의된 인터페이스를 통해 통신한다.
주문 서비스는 결제 서비스나 재고 서비스 내부 구현 대신 공개된 API에 의존한다. 내부 변경을 서비스 경계 안에 가두는 방식이다.
위임 메서드가 과도해지는 지점
이 원칙을 지나치게 적용하면 간접 호출을 위한 위임(delegation) 메서드가 늘어나고, 이른바 래퍼 지옥(wrapper hell)으로 이어질 수 있다. 협력 경로를 숨기는 일이 오히려 코드 이해를 어렵게 만들 수도 있다.
단순한 데이터 전달을 위한 쿼리 객체와 DTO는 원칙을 완화할 수 있다. 의도적으로 설계한 빌더 패턴과 메서드 체이닝도 예외로 볼 수 있으며, 상태가 변하지 않는 불변 객체는 내부 구조 노출에 따른 위험이 낮다.
협력 경로를 다듬는 패턴
Tell, Don't Ask는 객체의 상태를 조회한 뒤 외부에서 판단하기보다, 객체가 작업을 직접 수행하도록 요청하는 방식이다.
// 나쁜 예
if (user.hasPermission()) {
document.update(content);
}
// 좋은 예
user.updateDocument(document, content);
복잡한 서브시스템에는 파사드 패턴(Facade Pattern)으로 단순한 진입점을 제공할 수 있다. 호출자는 내부 서비스의 조율 과정을 알 필요가 없다.
// 파사드 패턴 예시
public class OrderFacade {
private InventoryService inventoryService;
private PaymentService paymentService;
private ShippingService shippingService;
public OrderResult processOrder(Order order) {
// 재고 확인, 결제 처리, 배송 처리를 내부적으로 조율
boolean isAvailable = inventoryService.checkAvailability(order.getItems());
if (!isAvailable) return OrderResult.INVENTORY_SHORTAGE;
PaymentResult paymentResult = paymentService.processPayment(order.getPaymentDetails());
if (!paymentResult.isSuccessful()) return OrderResult.PAYMENT_FAILURE;
ShippingInfo shippingInfo = shippingService.scheduleShipping(order);
return OrderResult.success(shippingInfo);
}
}
중재자 패턴(Mediator Pattern)은 객체가 서로 직접 통신하지 않고 중앙 객체를 통해 협력하도록 만들어 결합도를 낮춘다.
다른 객체 지향 원칙과 맞물리는 방식
단일 책임 원칙(SRP)은 객체가 다른 객체의 내부 구조에 간섭할 이유를 줄인다. 개방-폐쇄 원칙(OCP)은 낮은 결합도를 바탕으로 확장하기 쉬운 구조를 지향한다.
작고 집중된 인터페이스를 요구하는 인터페이스 분리 원칙(ISP)도 데메테르 법칙 준수를 돕는다. 의존성 역전 원칙(DIP)은 추상화에 의존하게 함으로써 구체 구현의 내부 구조를 알 필요를 낮춘다.
데메테르 법칙은 객체 간 책임을 적절히 나누고 협력 경로를 좁히는 기준이다. 변경과 테스트가 쉬운 구조를 만들되, 모든 접근을 무조건 감추기보다 코드의 유지보수성과 이해도를 함께 고려해야 한다.