디미터 법칙
디미터 법칙이란?
디미터 법칙Law of Demeter은 객체 간의 결합도를 줄이기 위한 원칙으로, 최소한의 정보로 협력하라는 규칙입니다. 즉, 객체는 자신이 직접적으로 알고 있는 객체와만 상호작용해야 합니다.
개념 설명
디미터 법칙은 객체가 직접 접근할 수 있는 정보만 사용하고, 간접적인 메서드 체인을 피하도록 하는 원칙입니다. 이를 통해 객체 간의 의존성을 줄이고, 코드 변경 시 발생할 수 있는 문제를 최소화할 수 있습니다.
예시
디미터 법칙을 위반한 예시
public class Order
{
public Customer Customer { get; set; }
public string GetCustomerStreet()
=> Customer.Address.Street; // 간접적인 접근
}
Order 클래스가 Customer 객체를 통해 Address 객체의 Street 속성에 직접 접근하고 있습니다. 이 방식은 여러 객체의 내부에 연속적으로 접근하는 방식으로, 객체 간의 결합도가 높아져 나중에 Customer나 Address 클래스가 변경되면 Order 클래스도 영향을 받을 수 있습니다.
디미터 법칙을 적용한 예시
public class Customer
{
public Address Address { get; set; }
public string GetStreet() => Address.Street;
}
public class Order
{
public Customer Customer { get; set; }
public string GetCustomerStreet() => Customer.GetStreet(); // 직접적인 접근만 사용
}
Order 클래스는 Customer 객체의 GetStreet() 메서드만 호출하여 객체 간 상호작용을 단순화했습니다. 이를 통해 Order 클래스는 Customer 클래스와만 상호작용하게 되며, Address 클래스의 변경에 영향을 받지 않게 됩니다.
책임 분리 및 결합도 감소 예시
다음으로, Address
클래스가 직접 Street
값을 반환하는 대신, 보다 캡슐화된 객체의 책임을 명확히 하기 위해 추가적인 개선을 할 수 있습니다. 예를 들어, Order
클래스가 고객의 주소와 관련된 정보를 여러 군데에서 사용해야 한다면, Order
자체에 책임을 넘기지 않고 적절한 객체에게 할당해야 합니다.
public class Address
{
private string Street { get; set; }
public Address(string street)
{
Street = street;
}
public string GetStreet() => Street;
}
public class Customer
{
public Address Address { get; set; }
public Customer(Address address)
{
Address = address;
}
// Customer는 Address 객체의 상세 정보를 위임받아 처리
public string GetCustomerAddress() => Address.GetStreet();
}
public class Order
{
public Customer Customer { get; set; }
public Order(Customer customer)
{
Customer = customer;
}
// Order는 Customer에게만 직접 접근하며, Address 객체와는 간접적으로만 상호작용
public string GetShippingAddress() => Customer.GetCustomerAddress();
}
이 코드에서는 책임이 더 명확하게 분리됩니다. Order
객체는 Customer
와만 상호작용하며, Address
에 대한 정보를 직접적으로 알지 못합니다. 이러한 설계는 객체 간 결합도를 낮추고, 각 객체가 자신의 책임을 다할 수 있도록 돕습니다.
실무적 개선 예시
실무에서는 여러 객체 간 협력이 더 복잡할 수 있습니다. 예를 들어, 고객 주소 외에 고객의 다른 정보(전화번호 등)를 함께 처리해야 할 경우가 있습니다. 이때 디미터 법칙을 따르면서도 객체 간 협력 관계를 유지하는 것이 중요합니다.
public class ContactInfo
{
public string PhoneNumber { get; set; }
public Address Address { get; set; }
public ContactInfo(string phoneNumber, Address address)
{
PhoneNumber = phoneNumber;
Address = address;
}
public string GetFullContactInfo()
=> $"Phone: {PhoneNumber}, Address: {Address.GetStreet()}";
}
public class Customer
{
public ContactInfo ContactInfo { get; set; }
public Customer(ContactInfo contactInfo)
{
ContactInfo = contactInfo;
}
public string GetCustomerContactInfo() => ContactInfo.GetFullContactInfo();
}
public class Order
{
public Customer Customer { get; set; }
public Order(Customer customer)
{
Customer = customer;
}
public string GetShippingDetails() => Customer.GetCustomerContactInfo();
}
여기서는 ContactInfo
객체를 도입하여 고객의 전화번호와 주소를 함께 관리하며, Order
객체는 Customer
객체를 통해 간접적으로 정보에 접근하게 됩니다. 이렇게 하면 다양한 정보를 처리하면서도 디미터 법칙을 유지할 수 있으며, 각 객체의 책임이 명확해집니다.
디미터 법칙의 장점
- 결합도 감소: 객체 간의 의존성을 줄여 코드 변경에 유연합니다.
- 유지보수성 향상: 변경 시 영향 범위가 줄어들어 코드가 더 안정적입니다.
- 가독성 증가: 간접적인 접근을 줄여 코드가 명확해집니다.
디미터 법칙 적용 시 유의할 점
지나친 위임 메서드 생성 방지
디미터 법칙을 적용하다 보면 간접 접근을 피하기 위해 위임 메서드를 많이 추가하게 되는데, 이는 오히려 코드의 복잡성을 높일 수 있습니다. 객체가 자신의 책임을 넘어 다른 객체의 메서드를 대신 호출하는 일이 많아지면, 객체의 역할이 모호해지며 코드의 유지보수가 어려워질 수 있습니다. 불필요한 위임 메서드를 남발하기보다는, 객체 간의 자연스러운 상호작용을 허용할 때가 더 나을 수 있습니다. 위임의 필요성과 코드 간소화 사이에서 적절한 균형을 유지하는 것이 중요합니다.
성능 최적화에 대한 고려
디미터 법칙을 적용하여 메서드 호출을 줄이는 대신, 중간 객체를 통한 위임이 추가되면 성능에 영향을 줄 수 있습니다. 성능이 중요한 시스템에서는 이러한 간접적인 호출이 누적될 경우 성능 저하로 이어질 수 있습니다. 특히 메서드 호출 비용이 큰 시스템에서는 직접적인 상호작용을 허용하는 것이 더 나을 수 있습니다. 성능이 중요한 경우에는 디미터 법칙을 엄격히 적용하기보다는 성능과 결합도를 적절히 조정하는 것이 좋습니다.
객체의 책임과 협력 구조 명확화
디미터 법칙을 적용하면서 객체 간 협력 관계를 지나치게 단절시키면, 객체가 자신의 핵심 책임을 수행하지 못하거나, 너무 많은 객체가 협력 없이 독립적으로 행동하게 될 수 있습니다. 객체 간의 상호작용을 지나치게 억제하지 않도록 주의해야 하며, 디미터 법칙이 적용되는 범위 내에서 객체의 책임이 명확하게 정의되도록 해야 합니다.
실무적 필요와 균형
디미터 법칙을 엄격하게 적용하는 것이 항상 우선순위가 아니며, 실무에서는 개발 속도와 요구사항을 고려한 유연한 적용이 필요합니다. 실제 업무 환경에서는 빠른 개발과 릴리즈가 중요한 경우가 많기 때문에, 법칙을 지나치게 엄격히 따르려다 보면 오히려 개발 속도를 저해할 수 있습니다. 법칙을 적용할 때 실무적 요구에 맞게 유연한 균형을 찾는 것이 중요합니다.
디미터 법칙의 한계
성능 저하
디미터 법칙을 적용할 때 발생하는 성능 문제는 대표적인 한계 중 하나입니다. 메서드 호출을 간접적으로 처리할 때, 위임 메서드가 증가하면서 불필요한 메서드 호출이 누적될 수 있습니다. 특히, 실시간 성능이 중요한 애플리케이션에서는 이러한 간접 호출로 인한 성능 저하가 시스템 전체의 퍼포먼스를 저해할 수 있습니다. 따라서 성능이 중요한 시스템에서는 디미터 법칙을 엄격하게 적용하기보다는 성능과 유지보수성 간의 균형을 고려해야 합니다.
유연성 저하
디미터 법칙을 너무 엄격히 적용하면, 객체 간 상호작용이 지나치게 제한되어 설계의 유연성이 떨어질 수 있습니다. 객체가 필요 이상의 간접 호출을 하게 되면, 실제로 필요할 때 직접적인 상호작용이 어려워져 설계가 불필요하게 복잡해질 수 있습니다. 이로 인해 설계가 경직되며, 이후 확장하거나 새로운 요구사항을 추가할 때 어려움이 발생할 수 있습니다.
위임 메서드로 인한 복잡성 증가
디미터 법칙을 지키기 위해 다수의 위임 메서드를 추가하는 경우, 불필요한 메서드가 과도하게 늘어날 수 있습니다. 이는 코드의 가독성과 유지보수성을 저해할 수 있으며, 오히려 관리하기 어려운 코드 구조가 될 수 있습니다. 위임 메서드의 과도한 사용은 설계의 간결성을 해치고, 불필요한 복잡성을 초래할 수 있기 때문에 적용 시 주의해야 합니다.
외부 API 및 라이브러리와의 충돌
디미터 법칙은 외부 API나 라이브러리와의 통합에서 문제가 발생할 수 있습니다. 외부 라이브러리나 프레임워크는 특정한 구조와 상호작용 방식을 요구하는 경우가 많습니다. 디미터 법칙을 준수하려다가 외부 API와의 통합을 어렵게 만들거나, 필요 이상의 복잡성을 추가하는 경우가 생길 수 있습니다. 이럴 때는 디미터 법칙을 너무 엄격하게 적용하지 않고, 상황에 맞게 융통성 있게 처리해야 합니다.
테스트 및 유지보수의 어려움
디미터 법칙을 엄격하게 적용하면, 객체의 행동이 간접적으로 위임되어 테스트하기 어려운 상황이 생길 수 있습니다. 여러 객체가 위임 메서드를 통해 간접적으로 상호작용할 때, 테스트 코드 작성이 어려워지거나, 문제가 발생했을 때 원인을 파악하는 데 시간이 더 걸릴 수 있습니다. 이러한 상황에서는 디미터 법칙을 일부 완화해 테스트의 용이성과 디버깅 효율성을 높일 필요가 있습니다.
맺음말
디미터 법칙은 객체 간의 불필요한 결합을 줄이고, 각 객체가 독립적으로 작동할 수 있도록 돕는 중요한 원칙입니다. 이를 통해 코드의 유지보수성과 안정성을 높일 수 있습니다.
심화 학습
디미터 법칙과 SOLID 원칙
디미터 법칙은 SOLID 원칙 중 단일 책임 원칙SRP과 의존 역전 원칙DIP과 밀접하게 연결되어 있습니다.
디미터 법칙은 객체가 너무 많은 외부 객체에 의존하지 않도록 설계하기 때문에 SRP와 같은 맥락에서 객체 간의 결합도를 낮추고 명확한 책임을 유지하게 합니다. DIP의 경우, 객체가 구체적인 클래스에 의존하기보다는 인터페이스나 추상화된 클래스에 의존하도록 유도하기 때문에, 디미터 법칙과 결합하여 유연한 의존성 관리와 결합도를 더욱 낮춘 설계를 할 수 있습니다.
디미터 법칙과 디자인 패턴
디미터 법칙을 효과적으로 구현하기 위해 여러 디자인 패턴을 적용할 수 있습니다. 특히, Facade 패턴과 Mediator 패턴은 디미터 법칙의 원칙을 보완하며 객체 간 상호작용을 중개하는 방식으로 결합도를 낮추는 역할을 합니다.
- Facade 패턴: 복잡한 하위 시스템을 감추고, 클라이언트와 상호작용할 수 있는 단순한 인터페이스를 제공하여 객체 간의 결합도를 줄입니다.
- Mediator 패턴: 객체들이 직접적으로 상호작용하지 않고, 중재자Mediator를 통해 소통함으로써 디미터 법칙을 적용하고 객체 간의 관계를 더욱 단순화할 수 있습니다.
디미터 법칙과 객체의 책임 분리
디미터 법칙을 적용하면, 객체의 책임이 보다 명확하게 정의될 수 있습니다. 객체가 직접적으로 다른 객체의 내부 상태를 참조하지 않고, 자신의 책임 내에서만 작업을 수행하기 때문에, 책임이 분리되고 유지보수가 쉬워집니다.
디미터 법칙과 리팩토링
리팩토링 과정에서 디미터 법칙을 적용하면 코드의 복잡성을 줄이고 결합도를 낮추는 좋은 기회가 됩니다. 특히, 기존 코드에서 메서드 체인이 길어지는 경우, 디미터 법칙에 따라 중간 객체들에 책임을 위임하는 방향으로 리팩토링할 수 있습니다. 리팩토링 전후의 결합도 변화와 객체 간 상호작용을 비교하며, 디미터 법칙이 가져오는 구조적인 변화를 실습하는 것이 유용합니다.