Law of Demeter(데메테르 법칙): 객체간 협력 경로 제한을 통한 결합도 감소 원칙
Law of Demeter(데메테르 법칙): 객체간 협력 경로 제한을 통한 결합도 감소 원칙
- 개요
- 데메테르 법칙의 정의
- 법칙의 핵심 원리
- 데메테르 법칙 위반 예시
- 데메테르 법칙을 준수한 개선 예시
- 데메테르 법칙의 이점
- 실제 적용 사례
- 데메테르 법칙의 균형점
- 구현 전략
- 데메테르 법칙과 다른 설계 원칙과의 관계
- 결론
- Keywords
개요
데메테르 법칙(Law of Demeter)은 객체 지향 프로그래밍에서 객체 간의 결합도를 낮추기 위한 중요한 설계 원칙이다. "최소 지식의 원칙(Principle of Least Knowledge)"이라고도 불리며, 객체가 다른 객체와 상호작용할 때 최소한의 지식만을 사용해야 한다는 개념에 기반한다. 이는 소프트웨어 유지보수성과 확장성을 크게 향상시키는 핵심 원칙이다.
데메테르 법칙의 정의
데메테르 법칙은 간단히 말해 "객체는 자신이 직접 관련된 객체와만 대화하라"는 원칙이다. 객체는 다음과 같은 대상에만 메시지를 보내야 한다:
- 객체 자신
- 메소드에 매개변수로 전달된 객체
- 메소드 내에서 생성된 객체
- 객체가 직접 관리하는 컴포넌트 객체
- 메소드 내에서 접근 가능한 전역 변수
법칙의 핵심 원리
객체는 간접적으로 연결된 다른 객체의 내부 구조에 대해 알아서는 안 된다. 이는 "낯선 사람에게 말하지 말라(Don't talk to strangers)"는 원칙으로도 표현된다. 객체가 다른 객체의 내부 구조에 깊이 관여할수록, 코드 변경 시 영향을 받는 범위가 넓어지고 유지보수가 어려워진다.
데메테르 법칙 위반 예시
다음은 Java에서 데메테르 법칙을 위반하는 코드 예시이다:
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()
와 같은 메소드 체인은 여러 객체의 내부 구조에 의존하고 있어 데메테르 법칙을 위반한다.
데메테르 법칙을 준수한 개선 예시
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)에 대해 알 필요가 없다. 이는 객체 간의 결합도를 크게 낮춘다.
데메테르 법칙의 이점
- 낮은 결합도: 객체 간의 의존성이 줄어들어 시스템의 유연성이 향상된다.
- 향상된 캡슐화: 객체의 내부 구현이 외부로부터 보호된다.
- 유지보수 용이성: 한 객체의 변경이 다른 객체에 미치는 영향이 최소화된다.
- 테스트 용이성: 독립적인 객체는 단위 테스트하기가 더 쉽다.
- 재사용성 증가: 느슨하게 결합된 객체는 다른 컨텍스트에서 재사용하기 쉽다.
실제 적용 사례
프레임워크 수준의 적용
Spring, Django와 같은 현대적 프레임워크들은 데메테르 법칙을 따르는 구조를 권장한다. 예를 들어 Spring의 서비스 계층은 데이터 접근 계층의 세부 구현에 직접 접근하지 않고, 인터페이스를 통해서만 상호작용한다.
마이크로서비스 아키텍처
마이크로서비스 아키텍처는 데메테르 법칙의 개념을 시스템 수준으로 확장한다. 각 서비스는 다른 서비스의 내부 구현에 대해 알지 못하며, 잘 정의된 인터페이스를 통해서만 통신한다.
graph TD
A[클라이언트] -->|API 호출| B[주문 서비스]
B -->|API 호출| C[결제 서비스]
B -->|API 호출| D[재고 서비스]
C -->|API 호출| E[외부 결제 게이트웨이]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333,stroke-width:2px
style C fill:#bbf,stroke:#333,stroke-width:2px
style D fill:#bbf,stroke:#333,stroke-width:2px
style E fill:#bfb,stroke:#333,stroke-width:2px
위 다이어그램에서 각 서비스는 다른 서비스의 내부 구현에 의존하지 않고, 공개된 API를 통해서만 통신한다.
데메테르 법칙의 균형점
데메테르 법칙을 맹목적으로 따르다 보면 때로 "래퍼 지옥(wrapper hell)"이라 불리는 상황이 발생할 수 있다. 객체 간의 간접 호출을 위한 위임(delegation) 메소드가 너무 많아지면 코드가 복잡해질 수 있다.
따라서 실용적인 균형점을 찾는 것이 중요하다:
- 쿼리 객체와 DTO: 단순히 데이터를 전달하는 객체에는 데메테르 법칙을 완화할 수 있다.
- 빌더 패턴과 메소드 체이닝: 의도적으로 설계된 메소드 체이닝은 데메테르 법칙의 예외로 볼 수 있다.
- 불변 객체: 상태가 변하지 않는 불변 객체의 경우 내부 구조 노출의 위험이 낮다.
구현 전략
1. Tell, Don't Ask 원칙 적용
객체의 상태를 물어보고(ask) 그 결과에 따라 결정하기보다, 객체에게 작업 자체를 수행하도록 지시(tell)하는 방식을 채택한다.
// 나쁜 예
if (user.hasPermission()) {
document.update(content);
}
// 좋은 예
user.updateDocument(document, content);
2. 파사드 패턴(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);
}
}
3. 중재자 패턴(Mediator Pattern) 활용
객체 간의 직접적인 통신을 중앙 객체를 통해 간접화하여 결합도를 낮춘다.
graph TD
A[객체 A] -->|통신| M[중재자]
B[객체 B] -->|통신| M
C[객체 C] -->|통신| M
M -->|통신| A
M -->|통신| B
M -->|통신| C
style M fill:#f96,stroke:#333,stroke-width:2px
데메테르 법칙과 다른 설계 원칙과의 관계
데메테르 법칙은 다른 객체 지향 설계 원칙들과 밀접하게 연관되어 있다:
- 단일 책임 원칙(SRP): 객체가 단일 책임을 가진다면, 다른 객체의 내부 구조에 간섭할 이유가 줄어든다.
- 개방-폐쇄 원칙(OCP): 객체 간의 낮은 결합도는 확장에 용이한 구조를 만든다.
- 인터페이스 분리 원칙(ISP): 작고 집중된 인터페이스는 데메테르 법칙을 준수하기 쉽게 만든다.
- 의존성 역전 원칙(DIP): 추상화에 의존함으로써 구체적인 구현의 내부 구조에 대한 지식 필요성이 줄어든다.
결론
데메테르 법칙은 "묻지 말고 시켜라(Tell, Don't Ask)"라는 객체 지향 철학을 실현하는 구체적인 지침이다. 이 법칙을 따름으로써 객체 간의 결합도가 낮아지고, 시스템의 모듈성과 유지보수성이 향상된다. 복잡한 시스템을 설계할 때 데메테르 법칙을 염두에 두면, 변경이 용이하고 테스트하기 쉬운 코드를 작성할 수 있다.
모든 설계 원칙과 마찬가지로, 데메테르 법칙도 상황과 맥락에 맞게 적절히 적용해야 한다. 맹목적인 적용보다는 코드의 유지보수성과 이해도를 높이는 방향으로 균형 있게 활용하는 것이 중요하다. 결국 데메테르 법칙은 객체 간의 적절한 책임 분배와 효과적인 협력 관계 구축을 통해 더 견고한 소프트웨어 시스템을 만드는 데 기여한다.
Keywords
Law of Demeter, 데메테르 법칙, 최소 지식 원칙, 객체지향설계, 결합도, encapsulation, 캡슐화, Tell Don't Ask, 객체간 협력, 책임 분배