디미터의 법칙

Jaca·2021년 11월 30일
0

디미터의 법칙이란?

객체는 그것이 내부적으로 보유하고 있거나 메시지를 통해 확보한 정보만 가지고 의사 결정을 내려야 한다

긴 객체 구조의 경로를 따라 멀리 떨어져 있는 간접적인(낯선) 객체에 메시지를 보내는(또는 이야기하는) 설계는 피하라는 것이다. 이러한 설계는 일반적으로 불안정한 지점으로, 객체 구조의 변화에 부서지기 쉽다.

어떤 객체가 다른 객체에 대해 지나치게 많이 알다보니, 결합도가 높아지고 좋지 못한 설계를 야기한다는 것을 발견하였다. 그래서 이를 개선하고자 객체에게 자료를 숨기는 대신 함수를 공개하도록 하였는데, 이것이 바로 디미터의 법칙이다.

이 것을 간단하게 말하면,

객체지향 생활 체조의 한 줄에 점을 하나만 찍는다. 로 귀결된다.

즉, 디미터의 법칙은 다른 객체가 어떠한 자료를 갖고 있는지 속사정을 몰라야 한다는 것을 의미한다.
또는 직관적으로 이해하기 위해 여러 개의 .(도트)을 사용하지 말라는 법칙으로도 많이 알려져 있으며, 디미터의 법칙을 준수함으로써 캡슐화를 높혀 객체의 자율성과 응집도를 높일 수 있다.

디미터의 법칙을 적용해보자

디미터 법칙은 객체의 모든 메서드는 다음에 해당하는 메서드만을 호출해야 한다고 말한다.

  • 자기 자신
  • 메서드로 넘어온 인자
  • 자신이 생성한 객체
  • 직접 포함하고 있는 객체

즉, 객체의 협력 경로를 제한해야 한다.

법칙을 잘 지킨 코드

디미터의 법칙을 따르는 형태의 코드는 다음과 같이 매우 단순하다.

object.method(param);

법칙을 지킬 필요가 없는 경우

하지만 객체가 자료구조라면 디미터의 법칙을 적용할 필요가 없다.

Options opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

위 코드가 법칙을 위반하는지는 ctxt, opts, scratchDir이 객체인지 자료구조인지에 달렸다.
객체라면 내부 구졸르 숨겨야하지만, 자료 구조는 내부 구조가 노출되어 있어 상관없다.

디미터의 법칙은 경우에 따라 판별이 매우 애매하므로 이분법적으로 나눌수 없다고 한다.

법칙을 위반한 코드 - 기차 충돌

디미터의 법칙을 따르지 않으면 메시지 체인이란게 발생한다고 한다.

object.getChild().getContent().getItem().getTitle();

getter 가 이어진 모습을 기차 충돌(train wreck) 이라고 표현한다.

이 기차 충돌이 문제가 되는 원인은,
간접적인 다른 객체에게 메시지를 보내기 위해 객체의 연결 경로를 따라 순회하괴 되면, 객체들이 어떻게 연결되어 있는지 적나라하게 보여지고, 프로그램 순회의 경로가 길어질수록 프로그램은 더 불안정해진다.
왜냐하면 객체 구조(연결) 방식은 변경될 수 있기 때문

한 줄에 하나의 .(도트)만 찍어라

위에도 언급했듯 객체지향 생활 체조에서 권장하는 코딩방식이다.
마치 디미터의 법칙과 동일해보이지만,
디미터의 법칙은 이것을 강제하진 않는다.

IntStream.of(1, 15, 20, 3, 9)
    .filter(x -> x > 10)
    .distinct()
    .count();

이 코드는 기차 충돌을 초래하기 때문에 디미터 법칙을 위반한다고 생각하기 쉽다.
하지만 of, filter, distinct 메서드는 모두 IntStream 이라는 동일한 클래스의 인스턴스를 반환한다. 즉, 이들은 IntStream의 인스턴스를 또다른 IntStream의 인스턴스로 변환한다.

따라서 이 코드는 디미터 법칙을 위반하지 않는다. 디미터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제가 되는 것은 객체의 내부 구조가 외부로 노출되는 경우로 한정된다.
결국 IntStream의 구조가 노출된 것은 아니기에 법칙을 위반하진 않았다.

하나 이상의 도트(.)를 사용하는 모든 케이스가 디미터 법칙 위반인 것은 아니다. 기차 충돌처럼 보이는 코드라도 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.

예시

아래와 같은 객체가 있다.

@Getter 
public class User {
    private String email; 
    private String name; 
    private Address address; 
} 

@Getter 
public class Address { 
     private String region; 
     private String details; 
}

어떤 User의 주소가 "서울"이라면 알림을 보내고자한다면, 아래와 같이 구현될 것이다.

@Service 
public class NotificationService { 
    public void sendMessageForSeoulUser(final User user) { 
        if("서울".equals(user.getAddress().getRegion())) { 
            sendNotification(user); 
        } 
    } 
}

가장 직관적이고 간단하지만 디미터의 법칙을 아주 제대로 위반하고 있다.

이 것을 디미터의 법칙을 준수하도록 변경해보자.

public class Address {
    private String region; 
    private String details; 
    
    public boolean isSeoulRegion() { 
        return "서울".equals(region); 
    } 
} 

public class User { 
    private String email; 
    private String name; 
    private Address address; 
    
    public boolean isSeoulUser() { 
        return address.isSeoulRegion(); 
    } 
}

위와 같이 클래스 내부에 메서드로 추출하는 것을 대리 객체 은폐(Hide DElegate) 라고 하며,
이에따른 메인 메서드는 아래와 같이 변경될 것이다.

@Service`
public class NotificationService { 
    public void sendMessageForSeoulUser(final User user) { 
        if(user.isSeoulUser()) { 
            sendNotification(user); 
        }
    }
}
profile
I am me

0개의 댓글